Monitor Java with JMX

JMX is a great way to monitor and manage any java application. Information about things like heap memory usage, garbage collection rate, and any custom data that the application supports can be retrieved. It is easy to enable and can be secured for remote access.

Enable JMX

By default, JMX is not enabled because it does introduce a security risk if not configured correctly. In order to enable it, a few flags need to be added to the command-line string that starts the application. This location varies by application as well as by platform and installation method. A typical tomcat installation has a $JAVA_OPTS variable that can be set in the supplied catalina.sh for things like this, which will be used here for demonstration purposes.

To enable JMX with no security options, use the following. Port 1616 is specified here, but there is no "standard" JMX port, so any unused port can be used.

JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1616"
JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"

Restart tomcat, and that's it! JMX is now ready to be queried. Fire up jconsole to see the statistics

jconsole localhost:1616

Security

The configuration above is really only safe for use in a secured network environment with no access to the JMX port from the outside. Anyone that has access to the open port has full access to the JMX subsystem listening there, and a simple port scan is all it takes to find it. To combat this, JMX provides security by means of SSL encryption as well as authentication via username/password or a client-side SSL certificate. Using a client-side SSL certificate is generally viewed as the more secure option, so that is what is demonstrated here.

Two java keystores and truststore pairs are required in order to use JMX over SSL with client-side certificate authentication. One pair to allow the client to trust the application, and the other to allow the application to trust the client. A keystore holds a private key and public certificate that identifies one side of the connection. A truststore holds a list of trusted public certificates which allows communication with whatever service has the matching private keys.

First, create the keystore and truststore for the application, tomcat in this case.

keytool -genkey -alias tomcat -keyalg RSA -validity 365 -keystore tomcat.keystore -storepass password -keypass password -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"
keytool -genkey -alias tomcat -keyalg RSA -validity 365 -keystore tomcat.truststore -storepass trustword -keypass trustword -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"

And do the same for the JMX client, jconsole in this case.

keytool -genkey -alias jconsole -keyalg RSA -validity 365 -keystore jconsole.keystore -storepass password -keypass password -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"
keytool -genkey -alias jconsole -keyalg RSA -validity 365 -keystore jconsole.truststore -storepass trustword -keypass trustword -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"

Then, export the public certificates from the keystores

keytool -export -alias tomcat -keystore tomcat.keystore -file tomcat.cer -storepass password
keytool -export -alias jconsole -keystore jconsole.keystore -file jconsole.cer -storepass password

Finally, import the certificates into the truststores. Again, this allows the application (tomcat) to trust the client (jconsole), and vice-versa.

keytool -import -alias jconsole -file jconsole.cer -keystore tomcat.truststore -storepass trustword -noprompt
keytool -import -alias tomcat -file tomcat.cer -keystore jconsole.truststore -storepass trustword -noprompt

Now, copy the tomcat keystore and truststore to the tomcat server, and modify the startup options to use it

JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1616"
JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=true"
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.keyStore=/path/to/tomcat.keystore -Djavax.net.ssl.keyStorePassword=password"
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/path/to/tomcat.truststore -Djavax.net.ssl.trustStorePassword=trustword"
JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.ssl.need.client.auth=true"

Restart tomcat for these options to take effect, and then connect with jconsole using

jconsole -J-Djavax.net.ssl.keyStore=/path/to/jconsole.keystore -J-Djavax.net.ssl.keyStorePassword=password -J-Djavax.net.ssl.trustStore=/path/to/jconsole.truststore -J-Djavax.net.ssl.trustStorePassword=trustword tomcathost:1616

The connection between the JMX client and the application is now encrypted, securely authenticated, and safe to use on any network, including the Internet.

References

  • http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#CreateKeystore
  • http://docs.oracle.com/javase/1.5.0/docs/guide/management/agent.html#SSL_enabled

Get in touch with us

Tell us about your project or drop us a line. We'd love to hear from you!