Posts 4 ways to secure JMX remote access
Post
Cancel

4 ways to secure JMX remote access

Many Java/JEE applications open the JMX interface/port to external for tuning or monitor purpose and most of them allow anonymous access. That is to say anyone knows the remote hostname and its JMX port can access/modify all exposed MBeans. I will show how to authenticate JMX access with following 4 options.

  • File-based Password
  • File-based PWD with SSL for Server Only
  • File-based PWD with SSL for Both Client & Server
  • Customized JMXAuthenticator

1. File-Based Password Authentication

Create a jmxremote.passowrd file in your specified directory and assign username/password. For example we set up a username “superadmin” with his password “agile9”.

1
2
## jmxremote.password
superadmin agile9

Create jmxremote.access file and assign JMX access privilege to superadmin user.

1
2
3
## jmxremote.access
monitorRole   readonly
superadmin    readwrite

Anyone can read these two files and get superadmin password if no permission is set. Also Java will throw error and end application with following error if these two files are not restricted.

1
2
Error: Password file read access must be restricted: 
	   /u01/agile/agile933/agileDomain/config/jmxremote.password

So we can do permission setting to allow only current user have the read/write privilege to it. In Linux we can have below.

1
2
3
4
5
6
[oracle@jielinux config]$ chmod 600 jmxremote.access
[oracle@jielinux config]$ chmod 600 jmxremote.password

[oracle@jielinux config]$ ls -l jmxremote.*
-rw-------. 1 oracle dba 67 Feb 13 22:07 jmxremote.access
-rw-------. 1 oracle dba 40 Feb 13 22:07 jmxremote.password

System Property Set to Server JVM

In remote Java application, we enable JMX Agent to read the specified password and access file with following parameters.

1
2
3
4
5
6
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=9899 
-Dcom.sun.management.jmxremote.authenticate=true 
-Dcom.sun.management.jmxremote.password.file=/u01/agile/agile933/agileDomain/config/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/u01/agile/agile933/agileDomain/config/jmxremote.access
-Dcom.sun.management.jmxremote.ssl=false

JMC Client Access

Run JConsole or Java VisualVM and input the specified credential to access remote JMX agent.


2. File-based PWD with SSL for Server Only

System Property Set to Server JVM

All the configurations are same like above except System Property set to JVM

1
2
3
4
5
6
7
8
9
10
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=9899
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=true
-Dcom.sun.management.jmxremote.password.file=/u01/agile/agile933/agileDomain/config/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/u01/agile/agile933/agileDomain/config/jmxremote.access
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false <-------------------------------------------
-Djavax.net.ssl.keyStore=/u01/agile/server.keystore
-Djavax.net.ssl.keyStorePassword=serverkeystorepassword
-Dcom.sun.management.jmxremote.registry.ssl=true

Export Server Certificate

1
keytool -export -alias agileserver -keystore /u01/agile/server.keystore -file /u01/agile/server.cer -storepass serverkeystorepassword

Import CA into JConsole’s truststore

1
keytool -import -alias agileserver-truststore -file server.cer -keystore jconsole.truststore -storepass changeit

Set JConsole Startup Parameter

1
jconsole -J-Djavax.net.ssl.trustStore=jconsole.truststore -J-Djavax.net.ssl.trustStorePassword=changeit


3. File-based PWD with SSL for Both Client & Server

System Property Set to Server JVM

1
2
3
4
5
6
7
8
9
10
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=9899
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=true
-Dcom.sun.management.jmxremote.password.file=/u01/agile/agile933/agileDomain/config/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/u01/agile/agile933/agileDomain/config/jmxremote.access
-Dcom.sun.management.jmxremote.ssl.need.client.auth=true    <-------------------------------------------
-Djavax.net.ssl.keyStore=/u01/agile/server.keystore
-Djavax.net.ssl.keyStorePassword=serverkeystorepassword
-Dcom.sun.management.jmxremote.registry.ssl=true

Export Server Certificate

Same as above

Import Server CA into JConsole’s truststore

Same as above

Export JConsole Certificate

Similar to above

Import JConsole CA into Server’s truststore

Similar to above

Set JConsole startup parameter

Same as above


4. Customized JMXAuthenticator

File-Based Password Authentication is simple, but SSL is expensive unless it is a self-signed. An enhanced authentication is to customize JMXAuthenticator. Following is a practical case that I develop a new LoginModule of javax.security.auth.spi.LoginModule to read Weblogic Server’s boot.properties, then authenticate JMX Client’s credential.

JAAS Configuration

Create a JAAS configuration file jmxauth.cfg and name a customized configuration entry JMXLogonConfig.

1
2
3
4
## jmxauth.cfg 
JMXLogonConfig{
com.agile.support.jmx.JMXLoginModule REQUIRED debug=true;
};

Customized LoginModule

First an authenticated Principal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## JMXPrincipal.java
package com.agile.support.jmx;
import java.security.Principal;
public class JMXPrincipal implements Principal {
	private String name;
	public JMXPrincipal(String name) {
		this.name = name;
	}
	public boolean equals(Object o) {
		return (o instanceof JMXPrincipal)this.name.equalsIgnoreCase(((JMXPrincipal) o).name);
	}
	public String getName(){
			return name;
	}
	public int hashCode(){
			return name.toUpperCase().hashCode();
	}
}

JMXLoginModule.java first reads JMX Client’s input (username and password), then encrypt the boot.properties’s password and compare these two. If they match, return the authenticated Principal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
## JMXLoginModule.java
package com.agile.support.jmx;
import java.util.Map;
import java.util.Properties;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import weblogic.security.internal.SerializedSystemIni;
import weblogic.security.internal.encryption.ClearOrEncryptedService;

public class JMXLoginModule implements LoginModule {
	private static final String passwordFile = System.getProperty("weblogic.system.BootIdentityFile");
	private boolean isAuthenticated = false;
	private CallbackHandler callbackHandler;
	private Subject subject;
	private JMXPrincipal principal;
	private Properties userCredentials;

	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map sharedState, Map options) {
		this.subject = subject;
		this.callbackHandler = callbackHandler;
		System.out.println("bigin");
	}

	public boolean login() throws LoginException {
		try {
			NameCallback nameCallback = new NameCallback("username");
			PasswordCallback passwordCallback = new PasswordCallback(
					"password", false);
			final Callback[] calls = new Callback[] { nameCallback,
					passwordCallback };
			callbackHandler.handle(calls);

			String username = nameCallback.getName();
			String password = String.valueOf(passwordCallback.getPassword());
			System.out.println("Got username/password from JMX Client");
			if (username != null && password != null) {
				username = username.trim();
				password = password.trim();
				
				loadPasswordFile();
				System.out.println("Got username/password from boot.properties");
				String filePwd = userCredentials.getProperty("password");
				String fileUserName = userCredentials.getProperty("username");
				
				System.out.println("Comparing with boot.properties");
				
				int l=passwordFile.lastIndexOf(File.separator);
				String securityPath = passwordFile.substring(0, l) + File.separator + "../security";
				
				ClearOrEncryptedService ces = new ClearOrEncryptedService(
					SerializedSystemIni.getEncryptionService(securityPath)
				);
				fileUserName = fileUserName.replace("\\", "");
				fileUserName =ces.decrypt(fileUserName);
				filePwd = filePwd.replace("\\", "");
				filePwd =ces.decrypt(filePwd);
				
				if (password.equals(filePwd) && username.equals(fileUserName)) {
					principal = new JMXPrincipal(username);
					isAuthenticated = true;
					System.out.println("JMX Client authenticated");
				} else {
					throw new LoginException("JMX Client not authenticated, user or password is wrong");
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
			throw new LoginException(
					"Failed to authenticate: no such user/no password file");
		}
		return isAuthenticated;
	}

	private void loadPasswordFile() throws Exception {
		FileInputStream fis;
		try {
			fis = new FileInputStream(passwordFile);
			BufferedInputStream bis = new BufferedInputStream(fis);
			userCredentials = new Properties();
			userCredentials.load(bis);
			bis.close();
		} catch (Exception e) {

			throw e;
		}
	}

	public boolean commit() throws LoginException {
		if (isAuthenticated) {
			subject.getPrincipals().add(principal);
		} else {
			throw new LoginException("Authentication failure");
		}
		return isAuthenticated;
	}

	public boolean abort() throws LoginException {
		return false;
	}

	public boolean logout() throws LoginException {
		subject.getPrincipals().remove(principal);
		principal = null;
		return true;
	}

}

The important code of above is how to decrypt boot.properties. securityPath points to the directory of SerializedSystem.dat file of Weblogic.

1
2
3
4
5
ClearOrEncryptedService ces = new ClearOrEncryptedService(SerializedSystemIni.getEncryptionService(securityPath));
fileUserName = fileUserName.replace("\\", "");
fileUserName =ces.decrypt(fileUserName);
filePwd = filePwd.replace("\\", "");
filePwd =ces.decrypt(filePwd);

Configuration to JMX Agent

Be sure to pack these two classes into a jar and include to classpath of Weblogic.

1
2
## setEnv.sh
$LIB_HOME/jmxlogin.jar

Then add JMX properties to JVM, inform the agent to read the specified JAAS entry: com.agile.support.jmx.JMXLoginModule

1
2
3
4
5
6
7
8
## startWeblogic.sh
-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=9899 
-Dcom.sun.management.jmxremote.authenticate=true 
-Djava.security.auth.login.config=/u01/agile/agile933/agileDomain/config/jmxauth.cfg 
-Dcom.sun.management.jmxremote.login.config=JMXLogonConfig 
-Dcom.sun.management.jmxremote.access.file=/u01/agile/agile933/agileDomain/config/jmxremote.access 
-Dcom.sun.management.jmxremote.ssl=false

JMX Client Access

Following message will be printed to Weblogic standard server output upon JMX Client’s correct credential.

1
2
3
4
Got username/password from JMX Client
Got username/password from boot.properties
Comparing with boot.properties
JMX Client authenticated
This post is licensed under CC BY 4.0 by the author.