Creating a secure JMX Agent in JDK 1.5

What is JMX?

Java Management Extension is an open technology for management, and monitoring that can be deployed wherever management and monitoring are needed. The most common use in a web application is for application management. This is very often an afterthought which results in many unmanaged application deployments.

You can monitor you application for availability and performance but in the same time you can use the JMX to manage and monitor you application from business perspective. Application’s runtime metrics can be expose through JMX, or in a service oriented architecture you could use JMX to control your services.

All good but when you start to work with JMX and JDK 1.5 soon you will discover one big limitation that was fixed in jdk 1.6 update 16 if i recall correctly:

Default RMI JMX agent for remote access opens 2 ports, one which is set by the -Dcom.sun.management.jmxremote.port=XXXX and one randomly assigned port.. What about firewalls?

JMX service url

service:jmx:rmi://hostname:port1/jndi/rmi//hostname:port2/jmxrmi

Where:


To access the RMIAgent you only need to know were the RMI registry is located from which to obtain the connection objects. I guess this was the reason to randomly assign the port1, but you have pretty high chances to have a firewall problem.

The solution is to replace the default agent and to create your own version of JMX Agent to provide access to RMI connection on a specific port.

JMX Architecture

There are three main components that makes the JMX possible:

Creating the Agent

To overcome the problems in JDK 1.5 explained above we need to create an java agent to export the RMI Registry and the RMIServer on specific ports.

This is pretty straight forward, we just need to export the RMIRegistry o a specific port, to get the MBeanServer and to create a JMXConnectorServer between those two. Then we just need to start the connector server and it is done

....................
LocateRegistry.createRegistry(port);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
final String hostname = InetAddress.getLocalHost().getHostName();
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"+hostname+":"+port+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi");
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
cs.start();

Simple as it gets, but right now we don;t have any security in place. If we want to add SSL and authorization things will complicate a little.

Securing the Agent

To make the access secure we have to expose the RMIRegisty over the SSL. For this we need to make the following modification:

..............
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
Registry registry = LocateRegistry.createRegistry(port, csf, ssf);
................

// Now specify the SSL Socket Factories:
//
// For the client side (remote)
//
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
// For the server side (local)
//
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
// For binding the JMX RMI Connector Server with the registry
// created above:
//
env.put("com.sun.jndi.rmi.factory.socket", csf);

final RMIServerImpl stub = new RMIJRMPServerImpl(port, csf, ssf, env);

final JMXConnectorServer cs =
new RMIConnectorServer(new JMXServiceURL("rmi", hostname, port),
env, stub, mbs) {
@Override
public JMXServiceURL getAddress() {
return url;
}
@Override
public synchronized void start() throws IOException {
try {
registry.bind("jmxrmi", stub);
} catch (AlreadyBoundException x) {
final IOException io = new IOException(x.getMessage());
io.initCause(x);
throw io;
}
super.start();
}
};
cs.start();

As you see we had to secure also the connection between ConnectorServer and JMX agent and for RMI server and client. Not that simple as the unsecure version. To add the authorization we just need to provide the credentials. Usually these are store in files and passed to the jmx connector server using environment variables:

env.put("jmx.remote.x.password.file", pasword);
env.put("jmx.remote.x.access.file", access);

And their content:

admin password1
monitor password2
..................
admin readwrite
monitor readonly

In order that SSL connections to work we need to create a keystore. This is done using keytool and can be create like this:

keytool -genkey -keyalg RSA -keysize 1024 -dname "CN=org.bserban.www" -keystore ./jmx-demo.jks -storepass bserban

Running the Agent

java -javaagent:jmx-agent.jar -Djavax.net.ssl.trustStore=./jmx-demo.jks -Djavax.net.ssl.trustPassword=bserban -Djavax.net.ssl.keyStore=./jmx-demo.jks -Djavax.net.ssl.keyStorePassword=bserban org.abserban.jmx.agent.StartAgentStandalone

Running the Client

java -Djavax.net.ssl.trustStore=./jmx-demo.jks -Djavax.net.ssl.trustPassword=bserban -Djavax.net.ssl.keyStore=./jmx-demo.jks -Djavax.net.ssl.keyStorePassword=bserban org.abserban.jmx.client.StartClient -host:localhost -port:8787 -status:on

See the comple code from Resources section. It includes the complete java code, ant file and the keystore.

Resources

Comments

Comments are closed.