<commit_before>= Securing remote JMX
:hp-tags: java
:published_at: 2016-09-01
:hp-image: images/logos/java.png

Like it or not, JMX is one the main tools for JVM monitoring.
If you are using Tomcat, Kafka or Cassandra you'll have to setup JMX tools to monitor them.

But JMX has several drawbacks:

* It's based on the obsolete RMI protocol
* It can trigger harmful functions like garbage collection
* It can be used for nasty exploits like https://issues.apache.org/jira/browse/COLLECTIONS-580[invoking arbitrary code]

So securing JMX is not an option.

== Going remote

By default, JMX is only locally accessible and secure: It can be accessed through Unix sockets.
This means you need to have access to the machine and run JMX tools with the same user as your application.
It's usually enough for development but not for production.

To enable remote JMX, the documentations tells you turn on some JVM flags:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.authenticate=false \
     Tomcat
----

The JVM will starting listening on *0.0.0.0:31419* for JMX requests.
Anybody will be able to plug any JMX tool (JConsole, JVisualVM, Mission Control...) from a remote machine.

If you have a firewall (IPTable or the like) to protect your server, and opened the 31419 TCP port,
or connecting through a tunnel, the JMX tools may fail to connect to the server.
At first, the JMX client connects to port *31419*, but gets redirected to a randomly chosen port (RMI WTF!).
To prevent this behaviour and force the JVM to use a single port:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.authenticate=false \
     Tomcat
----

== File based authentication

This configuration is nice for debugging but not secure at all.
Let's add authentication on this JMX connection.

First create a *password* file, similar to */etc/password*, containing login/password pairs:

[source]
.jmxremote.password
----
admin  adminpassword
user   userpassword
----

Then create an *access* file, similar to */etc/groups*, containing login/group pairs:

[source]
.jmxremote.access
----
admin readwrite <1>
user  readonly <2>
----
<1> admin has read/write access
<2> user  has read-only access

These two files should have limited access rights.

Finally, tell the JVM to use these files to authenticate users:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password \
     -Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access \
     Tomcat
----

== Using SSL

With this configuration, JVM tools have to provide a login and password to access to MBeans.
But, the password is sent over the wire without encryption.
Let's connect to JMX though SSL.

You may already known that, in the Java land,
private keys and trusted certificates are stored in wallets known as *keystores*,
and using the *JKS* file format.
A tool named *keytool* provided with the JDK is used to import/export keys and certs in the keystore.

Once the *keystore* (containing private key matching the server name along with certificate chain)
and the *truststore* (containing trusted certificates) are built,
just reference them:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password \
     -Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access \
     -Dcom.sun.management.jmxremote.registry.ssl=true \
     -Djavax.net.ssl.keyStore=/path/to/keystore.jks \
     -Djavax.net.ssl.keyStorePassword=<PASSWORD> \
     -Djavax.net.ssl.trustStore=/path/to/truststore.jks \
     -Djavax.net.ssl.trustStorePassword=<PASSWORD> \
     Tomcat
----

To connect, the JMX tools using SSL, you'll have to provide the trusted certificates.
For instance, to start the *JConsole*:

----
jconsole \
     -J-Djavax.net.ssl.trustStore=/path/to/truststore.jks \
     -J-Djavax.net.ssl.trustStorePassword=<PASSWORD>
----

== JMX configuration file

Again, this configuration has problem:

* Having passwords on the command line is not a good option,
because it's easy to use `ps` command to grab them.
* Having some many options on the command like is not elegant

Let's place all these options in a dedicated file:

.jmxremote.properties
----
com.sun.management.jmxremote=true
com.sun.management.jmxremote.port=31419
com.sun.management.jmxremote.rmi.port=31419
com.sun.management.jmxremote.password.file=/path/to/jmxremote.password
com.sun.management.jmxremote.access.file=/path/to/jmxremote.access
com.sun.management.jmxremote.registry.ssl=true
com.sun.management.jmxremote.ssl.config.file=/path/to/jmxremote.properties <1>

javax.net.ssl.keyStore=/path/to/keystore.jks
javax.net.ssl.keyStorePassword=<PASSWORD>
javax.net.ssl.trustStore=/path/to/truststore.jks
javax.net.ssl.trustStorePassword=truststore_password
----
<1> Path to file containing `javax.net.ssl.*` properties

And now, the command line sums up to

----
java -Dcom.sun.management.config.file=/path/to/jmxremote.properties \
     Tomcat
----

Such a JMX configuration file already exists in your JRE, it's named `JRE/lib/management/management.properties`.

== Some pointers

* https://docs.oracle.com/javase/8/docs/technotes/guides/management/(Java 8 JMX)
* https://www.jtips.info/index.php?title=JMX/Remote(JTips) in French<commit_msg>Add links in Securing JMX<commit_after>= Securing remote JMX
:hp-tags: java
:published_at: 2016-09-01
:hp-image: images/logos/java.png

Like it or not, JMX is one the main tools for JVM monitoring.
If you are using Tomcat, Kafka or Cassandra you'll have to setup JMX tools to monitor them.

But JMX has several drawbacks:

* It's based on the obsolete RMI protocol
* It can trigger harmful functions like garbage collection
* It can be used for nasty exploits like https://issues.apache.org/jira/browse/COLLECTIONS-580[invoking arbitrary code]

So securing JMX is not an option.

== Going remote

By default, JMX is only locally accessible and secure: It can be accessed through Unix sockets.
This means you need to have access to the machine and run JMX tools with the same user as your application.
It's usually enough for development but not for production.

To enable remote JMX, the documentations tells you turn on some JVM flags:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.authenticate=false \
     Tomcat
----

The JVM will starting listening on *0.0.0.0:31419* for JMX requests.
Anybody will be able to plug any JMX tool (JConsole, JVisualVM, Mission Control...) from a remote machine.

If you have a firewall (IPTable or the like) to protect your server, and opened the 31419 TCP port,
or connecting through a tunnel, the JMX tools may fail to connect to the server.
At first, the JMX client connects to port *31419*, but gets redirected to a randomly chosen port (RMI WTF!).
To prevent this behaviour and force the JVM to use a single port:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.authenticate=false \
     Tomcat
----

== File based authentication

This configuration is nice for debugging but not secure at all.
Let's add authentication on this JMX connection.

First create a *password* file, similar to */etc/password*, containing login/password pairs:

[source]
.jmxremote.password
----
admin  adminpassword
user   userpassword
----

Then create an *access* file, similar to */etc/groups*, containing login/group pairs:

[source]
.jmxremote.access
----
admin readwrite <1>
user  readonly <2>
----
<1> admin has read/write access
<2> user  has read-only access

These two files should have limited access rights.

Finally, tell the JVM to use these files to authenticate users:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.ssl=false \
     -Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password \
     -Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access \
     Tomcat
----

== Using SSL

With this configuration, JVM tools have to provide a login and password to access to MBeans.
But, the password is sent over the wire without encryption.
Let's connect to JMX though SSL.

You may already known that, in the Java land,
private keys and trusted certificates are stored in wallets known as *keystores*,
and using the *JKS* file format.
A tool named *keytool* provided with the JDK is used to import/export keys and certs in the keystore.

Once the *keystore* (containing private key matching the server name along with certificate chain)
and the *truststore* (containing trusted certificates) are built,
just reference them:

----
java -Dcom.sun.management.jmxremote=true \
     -Dcom.sun.management.jmxremote.port=31419 \
     -Dcom.sun.management.jmxremote.rmi.port=31419 \
     -Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password \
     -Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access \
     -Dcom.sun.management.jmxremote.registry.ssl=true \
     -Djavax.net.ssl.keyStore=/path/to/keystore.jks \
     -Djavax.net.ssl.keyStorePassword=<PASSWORD> \
     -Djavax.net.ssl.trustStore=/path/to/truststore.jks \
     -Djavax.net.ssl.trustStorePassword=<PASSWORD> \
     Tomcat
----

To connect, the JMX tools using SSL, you'll have to provide the trusted certificates.
For instance, to start the *JConsole*:

----
jconsole \
     -J-Djavax.net.ssl.trustStore=/path/to/truststore.jks \
     -J-Djavax.net.ssl.trustStorePassword=<PASSWORD>
----

== JMX configuration file

Again, this configuration has problem:

* Having passwords on the command line is not a good option,
because it's easy to use `ps` command to grab them.
* Having some many options on the command like is not elegant

Let's place all these options in a dedicated file:

.jmxremote.properties
----
com.sun.management.jmxremote=true
com.sun.management.jmxremote.port=31419
com.sun.management.jmxremote.rmi.port=31419
com.sun.management.jmxremote.password.file=/path/to/jmxremote.password
com.sun.management.jmxremote.access.file=/path/to/jmxremote.access
com.sun.management.jmxremote.registry.ssl=true
com.sun.management.jmxremote.ssl.config.file=/path/to/jmxremote.properties <1>

javax.net.ssl.keyStore=/path/to/keystore.jks
javax.net.ssl.keyStorePassword=<PASSWORD>
javax.net.ssl.trustStore=/path/to/truststore.jks
javax.net.ssl.trustStorePassword=<PASSWORD>
----
<1> Path to file containing `javax.net.ssl.*` properties

And now, the command line sums up to

----
java -Dcom.sun.management.config.file=/path/to/jmxremote.properties \
     Tomcat
----

Such a JMX configuration file already exists in your JRE, it's named `JRE/lib/management/management.properties`.

== Some pointers

* https://docs.oracle.com/javase/8/docs/technotes/guides/management/[Java 8 JMX]
* https://www.jtips.info/index.php?title=JMX/Remote[JTips] in French
{'fraction_non_alphanumeric': 0.10447379633574776, 'fraction_numerical': 0.014145717937792927, 'mean_word_length': 3.7303506650544134, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 24, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1776749', 'n_tokens_mistral': 3871, 'n_tokens_neox': 3668, 'n_words': 1161}