JSR 160 Security

Requiring user authentication to connect to a JMXConnectorServer

JSR 160 provides a pluggable authentication mechanism based on the javax.management.remote.JMXAuthenticator interface.
The JMXAuthenticator interface has only one method that takes credentials from the client and returns a javax.security.auth.Subject:

public Subject authenticate(Object credentials) throws SecurityException

An implementation of that interface may read username / password pairs from a file, and compare them with the supplied credentials (for example a String[2] where String[0] contains the username and String[1] contains the password).

MX4J provides a default implementation of the JMXAuthenticator interface, namely the mx4j.tools.remote.PasswordAuthenticator class. Refer to the PasswordAuthenticator javadoc for details.
It is possible to specify a file or a resource containing username/password pairs (where passwords can be in clear text or obfuscated via a digest algorithm) to the PasswordAuthenticator, that will then check correctness of the credentials passed by the client against this file or resource.

It is worth noting that JMXConnectorServers that offer a strong security (an example of a JMXConnectorServer that offers strong security is the - optional - javax.management.remote.jmxmp.JMXMPConnectorServer, whose security is based on SASL) may choose not to base their security system on JMXAuthenticator.

The JMXAuthenticator implementation must be provided to the JMXConnectorServer via the environment map:

Example 3.6. Using JMXAuthenticator in a JMXConnectorServer

// The address of the connector server
JMXServiceURL address = ...;

// The JMXAuthenticator implementation
JMXAuthenticator authenticator = ...;

// The environment map
Map environment = new HashMap();
environment.put(JMXConnectorServer.AUTHENTICATOR, authenticator);

// The MBeanServer to which the JMXConnectorServer will be attached to
MBeanServer server = MBeanServerFactory.createMBeanServer();

// Create the JMXCconnectorServer
JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, environment, server);

// Start the JMXConnectorServer

Once the JMXConnectorServer is setup correctly, it is possible to connect only by providing correct credentials from a JMXConnector.
The credentials must be serializable because they are provided by the JMXConnector and will be sent to the JMXConnectorServer to authenticate a Subject.
If the credentials provided are not correct, a SecurityException is raised.

Example 3.7. Passing credentials using a JMXConnector

// The address of the connector server
JMXServiceURL address = ...;

// Create the JMXCconnectorServer
JMXConnector cntor = JMXConnectorFactory.newJMXConnector(address, null);

// The credentials
Object credentials = ...;

// The connection environment map
Map environment = new HashMap();
environment.put(JMXConnector.CREDENTIALS, credentials);

// Connect and invoke an operation on the remote MBeanServer
catch (SecurityException x)
   // Uh-oh ! Bad credentials !
   throw x;

// Obtain a stub for the remote MBeanServer
MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

// Call the remote MBeanServer
ObjectName delegate = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");
String id = mbsc.getAttribute(delegate, "MBeanServerId");

Once correct credentials are provided the server side, the JMXAuthenticator can authenticate a Subject.
When you invoke operations on the MBeanServerConnection object obtained from the JMXConnector, the invocation end up on the server side, and over there the operation will be executed on behalf of the authenticated Subject.

If a SecurityManager is in place, you can specify different permissions for different authenticated subjects, fine tuning the security policy as you want.
You can for example grant all permissions to an "admin" principal, while granting only read access to MBean attributes to a "guest" principal.

If a SecurityManager is not in place, then using a JMXAuthenticator only allows you to deny access to non authenticated users: a simple way to restrict the access to the JMXConnectorServer.

Running under SecurityManager and Subject Delegation

Running a JMXConnectorServer under a SecurityManager allows to fine tune access to it from remote clients.

If you just want to restrict access to the JMXConnectorServer to authenticated users, but allow an authenticated user to perform any operation on the JMXConnectorServer and its underlying MBeanServer, then it is enough to provide a JMXAuthenticator while creating the JMXConnectorServer, as explained in the section above.

If instead, you want to be able to grant different permissions to different users, then you have to run the JMXConnectorServer under a SecurityManager and grant different permissions to different users.
For example, you may want the "admin" user to be able to perform any operation on the remote MBeanServer, and the "guest" user to be able to perform only read operations, and not write operations.

To show how to achieve this fine tuning, we will refer to example classes bundled with the MX4J distribution, namely mx4j.examples.remote.security.Client and mx4j.examples.remote.security.Server, and show how to setup the policy files in JDK 1.4 and JDK 1.3.
By default it will shown the sequence of operations for JDK 1.4, and differences with JDK 1.3 will be highlighted.

We will assume that the JMX implementation (mx4j.jar), the JMX Remote implementation (mx4j-remote.jar) and the MX4J examples (mx4j-examples.jar) are present in a directory called $WORK/lib, and that the example is started from the $WORK directory.
For JDK 1.3 you will also need jaas.jar in the $WORK/lib directory.

As first step, let's take a look at mx4j.examples.remote.security.Server.
It first creates a "users.properties" file with the "admin" and "guest" users under $WORK. This step is of course omitted in a real environment, where the "users.properties" file is provided externally.
Then it starts the NamingService and the JMXConnectorServer over RMI/JRMP, specifying a JMXAuthenticator that reads user names and passwords from the "users.properties" file.

Let's create now a policy file under $WORK, server.policy:

Example 3.8. The server.policy file

grant codebase "file:${user.dir}/lib/mx4j-remote.jar"
   permission java.security.AllPermission;
grant codebase "file:${user.dir}/lib/mx4j.jar"
   permission java.security.AllPermission;
grant codebase "file:${user.dir}/lib/mx4j-tools.jar"
   permission java.security.AllPermission;

/* Uncomment for JDK 1.3
grant codebase "file:${user.dir}/lib/jaas.jar"
   permission java.security.AllPermission;

grant codebase "file:${user.dir}/lib/mx4j-examples.jar"
   permission java.security.AllPermission;

Notice how we grant AllPermission to the MX4J jars that implement JMX and JMX Remote, and to the MX4J Tools jar (and to JAAS jar for JDK 1.3).
You can consider those jars as libraries, and you trust that the MX4J implementation, as well as the JAAS implementation, won't harm your system.

Different is the motivation behind granting AllPermission to the MX4J Examples jar, which is the one that containes mx4j.examples.remote.security.Server.
For this example we grant AllPermission because we trust the Server class not to perform any security sensitive operation beyond starting the JMXConnectorServer and reading the "users.properties" file, but you may want to restrict the set of operations performed by the classes that start the JMXConnectorServer.
With the above server.policy file, you can successfully start the mx4j.examples.remote.security.Server class, in this way:

Example 3.9. Starting the secured server

$WORK>java -Djava.security.manager
           -Djava.security.auth.policy=jaas.server.policy    --> Only for JDK 1.3
           -classpath lib/mx4j-examples.jar;                 \
                      lib/mx4j.jar;                          \
                      lib/mx4j-remote.jar                    \
                      lib/jaas.jar                           --> Only for JDK 1.3

Note the double equals sign '=' after the property "java.security.policy": see the documentation guide about policy files for the JDK for further information about the meaning of this double equals sign.

It is time now to grant permissions for the operations a remote client that is connecting may want to do.
Since we specified a JMXAuthenticator, only authenticated users can connect to the JMXConnectorServer.
Furthermore, since we run under a SecurityManager, authenticated users may only perform the operations granted to the corrispondent principal in the policy file.

For JDK 1.3, these permissions must be specified in a separate file, named here "jaas.policy.file", referenced by the "java.security.auth.policy" system property at startup time (see above).
For JDK 1.4, these permissions can be added to the "server.policy" file.

Example 3.10. The principal-specific permissions

   // Required by MX4J implementation
   permission javax.security.auth.AuthPermission "doAsPrivileged";

   // Required to all clients to connect to the server
   permission java.net.SocketPermission "localhost:1024-", "listen,resolve";
   permission java.net.SocketPermission "*", "accept,resolve";

grant principal javax.management.remote.JMXPrincipal "admin"
   permission java.security.AllPermission;

grant principal javax.management.remote.JMXPrincipal "guest"
   permission javax.management.MBeanPermission "*", "queryNames";
   permission javax.management.remote.SubjectDelegationPermission "javax.management.remote.JMXPrincipal.*";

First of all, note that there is no specification of the codebase.
It is not possible to specify the codebase for permissions you want to grant to a JSR 160 connector server, you have to omit it.

Second, note that there are three grant blocks: one that specifies permissions common to all principals, one that specifies permissions for the "admin" principal, and one that specifies permissions for the "guest" principal.

The common block allows anyone to connect to the server side, as specified by the two SocketPermissions.

The "admin" principal has AllPermission, while the "guest" principal can only call the javax.management.MBeanServerConnection.queryNames() method.

In the same exact way, you can specify many principal blocks, one per principal, with a different set of permission for each one; this allows to fine tune the permissions granted to each principal that connects to the server side.

Lastly, note the SubjectDelegationPermission. This permission is needed when you have, on client, a JAAS Subject and you want to perform, on server side, operations on behalf of that Subject.
For example, it is possible that on a client there are many users that want to interact with the server side, and that all users have been granted the same set of permissions.
In this case is more efficient to establish only one connection with the server side, with an authenticated user (in the example above, "guest"), and then use that connection with many delegate users, by using javax.management.remote.JMXConnector.getMBeanServerConnection(Subject delegate).
In the example above, the authenticated principal "guest" allows any delegate principal (of class javax.management.remote.JMXPrincipal) to perform operations in its behalf, since it grants a SubjectDelegationPermission with a name of "javax.management.remote.JMXPrincipal.*" (refer to the JMX Remote javadocs for explanation of the syntax of SubjectDelegationPermission).