Java Dynamic Management Kit 5.1 Tutorial

Chapter 11 Connector Security

Whenever considering a distributed architecture, security issues are often an added factor in the complexity of the design. This is not the case with the Java Dynamic Management Kit (Java DMK), whose security features are built into the modularity of the components.

Management solutions can evolve from basic password-based protection all the way to secure connections using cryptography simply by adding components to your connector protocols. The rest of the architecture is unchanged because it relies on the interface that is common to all connectors.

The code samples in this chapter are taken from the files in the current/Security example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:


Note –

Most of the examples in this chapter implement a SASL mechanism. For more information about SASL, see the The Java SASL API Programming and Deployment Guide at the following URL.

http://java.sun.com/j2se/1.5.0/docs/guide/security/sasl/sasl-refguide.html


11.1 Simple Security

The simplest type of connector security you can use with the Java DMK is based upon encryption, user name and password authentication, and file access control. The exact implementation of these security features depends on whether you are using an RMI or a JMXMP connector, as explained in the following sections. The examples make use of the secure sockets layer (SSL) RMI socket factories and the SASL classes that are provided with Java DMK.

11.1.1 RMI Connectors With Simple Security

You can find an example of an RMI connector with simple security in the directory examplesDir/current/Security/rmi/simple. The properties files access.properties and password.properties are found in the config directory inside examplesDir/current/Security/rmi/simple.

The RMI connector server is created by the Server class, shown below.


Example 11–1 Creating an RMI Connector Server with Simple Security

public class Server { 
 
  public static void main(String[] args) { 
  try { 
       MBeanServer mbs = MBeanServerFactory.createMBeanServer(); 
 
       HashMap env = new HashMap(); 
 
       SslRMIClientSocketFactory  csf =  
                  new SslRMIClientSocketFactory(); 
       SslRMIServerSocketFactory ssf =  
                  new SslRMIServerSocketFactory(); 
       env.put(RMIConnectorServer. 
                  RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,csf); 
       env.put(RMIConnectorServer. 
                  RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,ssf); 
 
       env.put("jmx.remote.x.password.file", 
                 "config" + File.separator + "password.properties"); 
       env.put("jmx.remote.x.access.file", 
                 "config" + File.separator + "access.properties"); 
 
       JMXServiceURL url = new JMXServiceURL( 
        "service:jmx:rmi:///jndi/rmi://localhost:9999/server"); 
         JMXConnectorServer cs = 
            JMXConnectorServerFactory.newJMXConnectorServer(url,  
                                                            env,  
                                                            mbs); 
       cs.start(); 
     } catch (Exception e) { 
       e.printStackTrace(); 
     } 
  } 
} 

The Server class shown in Example 11–1 creates an MBean server mbs, and populates an environment map env with a secure RMI client socket factory csf, a secure RMI server socket factory ssf, and the properties files password.properties and access.properties.

The properties file password.properties contains a username and password and is accessed using the JMXAuthenticator interface. Using the property jmx.remote.x.password.file is the same as creating a password-based JMXAuthenticator and passing it into the environment map through the jmx.remote.authenticator property.

The properties file access.properties contains a username and a level of access permission that can be either readwrite or readonly. This represents the level of access this user can have to MBean server operations. This file-based access control is implemented using the interface MBeanServerForwarder, which wraps the real MBean server inside an access controller MBean server. The access controller MBean server only forwards requests to the real MBean server after performing the appropriate checks.

Because the connector uses SSL, when you start the Server class, you will have to provide it with a pointer to the SSL keystore file and provide the password expected by the SSL RMI server socket factory ssf. This is shown in Example 11–2 below.

Server creates a service URL, named url, for an RMI connector that will operate over the default JRMP transport, and register an RMI connector stub in an RMI registry on port 9999 of the local host.

The MBean server mbs, the environment map env and the service URL url are all passed to JMXConnectorServer to create a new, secure connector server named cs.


Example 11–2 Creating an RMI Connector Client with Simple Security

public class Client { 
 
  public static void main(String[] args) { 
  try { 
      HashMap env = new HashMap(); 
 
      String[] credentials = new String[] { "username" , "password" }; 
      env.put("jmx.remote.credentials", credentials); 
      JMXServiceURL url = new JMXServiceURL( 
         "service:jmx:rmi:///jndi/rmi://localhost:9999/server");       
      JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
      MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); 
      String domains[] = mbsc.getDomains(); 
      for (int i = 0; i < domains.length; i++) { 
         System.out.println("Domain[" + i + "] = " + domains[i]); 
      } 
       
      ObjectName mbeanName =  
          new ObjectName("MBeans:type=SimpleStandard"); 
      mbsc.createMBean("SimpleStandard", mbeanName, null, null); 
      // Perform MBean operations 
      [...] 
      
      mbsc.removeNotificationListener(mbeanName, listener); 
      mbsc.unregisterMBean(mbeanName); 
      jmxc.close(); 
    }  catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
} 
 

The Client class shown in Example 11–2 populates an environment map env with a set of credentials, namely the username and password expected by the Server. These credentials are then given to an instance of JMXConnector named jmxc when the service URL of the connector stub and the environment map are passed to JMXConnectorFactory.connect(). Through jmxc, the Client connects to the MBean server started by Server, and performs operations on the MBean SimpleStandard.

When the connection is established, the credentials supplied in the environment map env are sent to the server. The server then calls the authenticate() method of the JMXAuthenticator interface, passing the client credentials as parameters. The authenticate() method authenticates the client and returns a subject that contains the set of principals upon which the access control checks will be performed.

Because the connector uses SSL, when you start the Client class, you will have to provide it with a pointer to the SSL truststore file and provide the password, trustword, that is expected by the SSL RMI server socket factory ssf. This is shown in To Run the RMI Connector Example With Simple Security below.

To Run the RMI Connector Example With Simple Security

Run this example from within the examplesDir/current/Security/rmi/simple directory. Before running the example, make sure your classpath contains the runtime library for SSL RMI (see Directories and Classpath).

  1. Compile the example classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java
    
  2. Start an RMI registry on port 9999 of the local host.


    $ export CLASSPATH=server:classpath ; rmiregistry 9999 &
    
  3. Start the Server.

    You have to provide the SSL keystore and its password when you start the Server


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         Server &  
    

    You will see confirmation of the creation of the MBean server and of the RMI connector.

  4. Start the Client.

    The Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:server:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client 
    

    You will see confirmation of the creation of the connector client, the various MBean operations and finally the closure of the connection.

    As you can see, all the above appears to proceed in exactly the same manner as the basic RMI connector example shown in Chapter 9, Protocol Connectors. However, if you were to open password.properties and change the password, you would see a java.lang.SecurityException when you launched the Client, and the connection would fail.

11.1.2 JMXMP Connectors With Simple Security

You can find an example of a JMXMP connector with simple security in the directory examplesDir/current/Security/jmxmp/simple. JMXMP connectors can be secured using Java DMK's own implementation of the Simple Authentication and Security Layer (SASL), which provides a greater level of security than the SSL security implemented by the RMI connector.


Example 11–3 Creating a JMXMP Connector Server with Simple Security

public class Server { 
 
    public static void main(String[] args) { 
      try { 
           MBeanServer mbs = MBeanServerFactory.createMBeanServer(); 
           HashMap env = new HashMap(); 
          	     
           Security.addProvider(
                 new com.sun.jdmk.security.sasl.Provider()); 
           env.put("jmx.remote.profiles", "TLS SASL/PLAIN"); 
           env.put("jmx.remote.sasl.callback.handler", 
               new PropertiesFileCallbackHandler("config" +  
                                               File.separator +  
                                               "password.properties")); 
           env.put("jmx.remote.x.access.file", 
                 "config" + File.separator + "access.properties"); 
 
           JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
           JMXConnectorServer cs = 
              JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                              env, 
                                                              mbs); 
           cs.start(); 
 
         } catch (Exception e) { 
           e.printStackTrace(); 
         } 
      } 
  } 
 

Example 11–3 shows the creation of an MBean server mbs, and an environment map env. A security provider is defined by a call to the addProvider() method of the Security class, implementing server support for the PLAIN SASL mechanism to Java DMK.

The environment map env is populated with the TLS and PLAIN SASL profiles. A callback handler, an instance of the PropertiesFileCallbackHandler class, is also passed to the environment map. The PropertiesFileCallbackHandler class is an implementation of the CallbackHandler interface, and is used by Server to check that the user name and password supplied by the client against the user name and password supplied in the password.properties file.

Finally, the access properties file, access.properties, is passed into the environment map to define the names of the users who will be authorized to access the connector server, and their level of access.

When the security provider and the environment map have been configured, a service URL, named url, is created. This URL is defined by the constructor JMXServiceURL, with its three arguments specifying JMXMP as the connector protocol, a null host name, and port number 5555 as the port upon which the connector server will listen for connections. The URL is then provided, along with the environment map env, to create an instance of JMXConnectorServer, named cs. The connector server cs is started by calling the start() method of JMXConnectorServer.


Example 11–4 Creating a JMXMP Connector Client with Simple Security

public class Client { 
 
   public static void main(String[] args) { 
     try { 
          HashMap env = new HashMap(); 
          Security.addProvider(new com.sun.security.sasl.Provider()); 
          env.put("jmx.remote.profiles", "TLS SASL/PLAIN"); 
          env.put("jmx.remote.sasl.callback.handler", 
             new UserPasswordCallbackHandler("username", "password")); 

          JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
          JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
          MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); 
          String domains[] = mbsc.getDomains(); 
          for (int i = 0; i < domains.length; i++) { 
          System.out.println("Domain[" + i + "] = " + domains[i]); 
          } 
 
          ObjectName mbeanName = 
              new ObjectName("MBeans:type=SimpleStandard"); 
          mbsc.createMBean("SimpleStandard", mbeanName, null, null); 
          // Perform MBean operations 
          // 
          [...] 
          mbsc.removeNotificationListener(mbeanName, listener); 
          mbsc.unregisterMBean(mbeanName); 
          jmxc.close(); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
     } 
 } 

In Example 11–4, we see the creation of an environment map env, and a security provider is defined by a call to the addProvider() method of the Security class, implementing the client support for the PLAIN SASL mechanism.

A callback handler, an instance of the UserPasswordCallbackHandler class, is passed to the environment map for use by the PLAIN SASL client mechanism to retrieve the user name and password of the remote user connecting to the server.

Once the security provider and the environment map have been configured, a service URL named url is created. This URL defines JMXMP as the connector protocol, and port number 5555 as the port upon which the connector server will make connections. The URL is then provided, along with the environment map env, to create an instance of JMXConnector, named jmxc. An MBean server connection, mbsc, is made by calling the getMBeanServerConnection of the connector jmxc.

In code that is not shown here, when the secure connection to the MBean server has been established, the Client creates an MBean called SimpleStandard and performs various operations on it. Once these MBean operations have completed, the Client unregisters the MBean, and closes down the connection jmxc.

To Run the JMXMP Connector Example with Simple Security

Run this example from within the examplesDir/current/Security/jmxmp/simple directory. Before running the example, make sure your classpath contains the runtime library for SASL (see Directories and Classpath).

  1. Compile the example classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          server/PropertiesFileCallbackHandler.java \
          client/Client.java \
          client/ClientListener.java \
          client/UserPasswordCallbackHandler.java
    
  2. Start the Server:

    The Server requires the SSL keystore file and its password when you launch it.


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         Server &
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector.

  3. Start the Client:

    The Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

    As you can see, all the above appears to proceed in exactly the same manner as the basic JMXMP connector example shown in Chapter 9, Protocol Connectors. However, if you were to open access.properties and change the access to readonly rather than readwrite, you would see a java.lang.SecurityException when you launched the Client, and the connection would fail.

11.2 Subject Delegation

If your implementation requires the client end of the connection to perform different operations on behalf of multiple users or applications, if you were to use the security mechanisms demonstrated in 11.1 Simple Security, each different user would require one secure connection for every operation it performs. If you expect your connector clients to interact with numerous users, you can reduce the load on your system by implementing subject delegation. Subject delegation establishes a single secure connection for a user, and this connection can be used to perform related operations on behalf of any number of users. The connection itself is made by an authenticated user. If the authenticated user has been granted a SubjectDelegationPermission that allows it to act on behalf of another user, then operations can be performed over the connection on behalf of that user.

11.2.1 Secure RMI Connectors With Subject Delegation

You can find an example of a secure RMI connector that implements subject delegation in the directory examplesDir/current/Security/rmi/subject_delegation.

The Server class used in this example is identical to the one used in 11.1 Simple Security. However, unlike the simple security example, this example implements a java.policy file to grant permission to certain users to perform certain actions.


Example 11–5 A java.policy File

grant codeBase "file:installDir/lib/jmxremote.jar" {
    permission javax.management.remote.SubjectDelegationPermission 
             "javax.management.remote.JMXPrincipal.delegate";
};

grant codeBase "file:server" {
    permission javax.management.remote.SubjectDelegationPermission 
              "javax.management.remote.JMXPrincipal.delegate";
};

grant principal javax.management.remote.JMXPrincipal "username" {
     permission javax.management.remote.SubjectDelegationPermission 
              "javax.management.remote.JMXPrincipal.delegate";
};

The java.policy file grants a user named username a SubjectDelegationPermission so it can perform operations on behalf of the user delegate, an instance of JMXPrincipal created by the Client class. The java.policy file is required when launching the Server class. The creation of the JMXPrincipal instance by the Client is shown below.


Example 11–6 Creating a Delegation Subject

[...]

JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
Subject delegationSubject = 
            new Subject(true, 
                Collections.singleton(new JMXPrincipal("delegate")), 
                Collections.EMPTY_SET, 
                Collections.EMPTY_SET); 
 
MBeanServerConnection mbsc = 
          jmxc.getMBeanServerConnection(delegationSubject); 
[...]

The Client class is identical to the one used in the simple security example, except for the creation of the delegation subject shown in Example 11–6.

As before, the Client creates an environment map env that is populated with a user name username and a password password. These strings match the user name and password stored in the password.properties file that is held by the Server to authenticate users accessing the connector server.

A connector client jmxc is created in the same way as in the previous RMI connector examples, with the user name and password passed into the environment map env.

The Client then creates an instance of Subject, called delegationSubject, with a Principal that is an instance of JMXPrincipal, named delegate.

An MBean server connection, named mbsc, is created by calling the getMBeanServerConnection() method of JMXConnector, with delegationSubject passed in as a parameter. This MBean server connection therefore allows operations to be performed on the remote MBean server on behalf of the principals stored in the delegationSubject, which in this example is the JMXPrincipal named delegate.

The example continues by creating and registering the SimpleStandard MBean in the MBean server, and performing operations on it, in exactly the same way as in the previous examples.

To Run the Secure RMI Connector Example With Subject Delegation

Run this example from within the examplesDir/current/Security/rmi/subject_delegation directory.

  1. Compile the example classes.


    $ javac -classpath classpath \ 
      mbeans/SimpleStandard.java \ 
      mbeans/SimpleStandardMBean.java \ 
      server/Server.java \ 
      client/Client.java \ 
      client/ClientListener.java 
    
  2. Start an RMI registry on port 9999 of the local host.


    $ export CLASSPATH=server:classpath ; rmiregistry 9999 &
    
  3. Create a java.policy file from the java.policy.template file in the config directory.

    You must replace @INSTALL_HOME_FOR_JDMK@ with your installDir.

  4. Start the Server.

    As was the case with the simple secure RMI connector example, you have to provide the SSL keystore and its password when you start the Server, as well as the a pointer to the java.policy that grants permission to the delegate subject.


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         -Djava.security.policy=config/java.policy Server &
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map, the creation of the RMI connector, and the registration of the connector in the MBean server.

  5. Start the Client.

    Again, the Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:server:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client
    

    You will see confirmation of the creation of the connector client, the creation of the delegation subject, the connection to the MBean server and the various MBean operations followed by the closure of the connection.

11.2.2 Secure JMXMP Connectors With Subject Delegation

The example of JMXMP connectors with subject delegation is mostly identical to the example of a simple secure JMXMP connector. The only differences are in the client end of the connection, in which the delegation subject is defined, and in the java.policy file used to grant permission to the delegation subjects created.

In this example, the Server class creates an MBean server, and a connector server cs, again protected by an SSL password, as was the case in the simple secure JMXMP connector example.

The Client creates a connector client named jmxc in the same way as in the previous JMXMP connector examples. The Client then creates an instance of Subject, called delegationSubject, with a Principal that is an instance of JMXPrincipal, named delegate.

An MBean server connection, named mbsc, is created by calling the getMBeanServerConnection() method of JMXConnector, with delegationSubject passed in as a parameter. This MBean server connection therefore allows operations to be performed on the remote MBean server on behalf of the principals stored in the delegationSubject, which in this example is the JMXPrincipal named delegate.

To Run the Secure JMXMP Connector Example With Subject Delegation

Run this example from within the examplesDir/current/Security/jmxmp/subject_delegation directory.

  1. Compile the example classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          server/PropertiesFileCallbackHandler.java \
          client/Client.java \
          client/ClientListener.java \
          client/UserPasswordCallbackHandler.java
    
  2. Create a java.policy file from the java.policy.template file in the config directory.

    You must replace @INSTALL_HOME_FOR_JDMK@ with your installDir.

  3. Start the Server.

    The Server requires the SSL keystore file and its password, and a pointer to the java.policy file when you launch it.


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         -Djava.security.policy=config/java.policy Server &
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector.

  4. Start the Client.

    Again, the Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the creation of the delegation subject, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.3 Fine-Grained Security

You can implement a more fine-grained level of security in your connectors by managing user access through the Java Authentication and Authorization Service (JAAS) and Java 2 platform Standard Edition (J2SE) Security Architecture. JAAS and J2SE security is based on the use of security managers and policy files to allocate different levels of access to different users. Consequently, you can decide more precisely which users are allowed to perform which operations.

The two examples in this section are very similar to those shown in 11.1 Simple Security, with the difference being that, in addition to SSL encryption, the simple, file-based access control has been replaced by policy-based access control.

11.3.1 RMI Connector With Fine-Grained Security

You can find an example of an RMI connector with fine-grained security in the directory examplesDir/current/Security/rmi/fine_grained.

The Server class used in this example is very similar to the one used in the RMI connector example with simple security. The only difference is that there is no access.properties file to map into the environment map in the fine-grained example. This was omitted so as not to make the example overly complicated. Otherwise, all the other classes and files used in this example are the same as those used in 11.1.1 RMI Connectors With Simple Security, with the exception of the java.policy file, which is shown below.


Example 11–7 A java.policy File for an RMI Connector With Fine-Grained Security

grant codeBase "file:installDir/lib/jmx.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/jmxremote.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:server" {
    permission java.security.AllPermission;
};

grant codeBase "file:mbeans" {
    permission javax.management.MBeanTrustPermission "register";
};

grant principal javax.management.remote.JMXPrincipal "username" {
    permission javax.management.MBeanPermission "*", "getDomains";
    permission javax.management.MBeanPermission 
             "SimpleStandard#-[-]",  "instantiate";
    permission javax.management.MBeanPermission 
             "SimpleStandard#-[MBeans:type=SimpleStandard]", 
             "registerMBean";
    permission javax.management.MBeanPermission 
             "SimpleStandard#State[MBeans:type=SimpleStandard]", 
             "getAttribute";
    permission javax.management.MBeanPermission 
             "SimpleStandard#State[MBeans:type=SimpleStandard]", 
             "setAttribute";
    permission javax.management.MBeanPermission 
             "SimpleStandard#-[MBeans:type=SimpleStandard]", 
             "addNotificationListener";
    permission javax.management.MBeanPermission 
             "SimpleStandard#reset[MBeans:type=SimpleStandard]", 
             "invoke";
    permission javax.management.MBeanPermission 
              "SimpleStandard#-[MBeans:type=SimpleStandard]", 
              "removeNotificationListener";
    permission javax.management.MBeanPermission 
              "SimpleStandard#-[MBeans:type=SimpleStandard]", 
              "unregisterMBean";
    permission javax.management.MBeanPermission 
              "javax.management.MBeanServerDelegate#
              -[JMImplementation:type=MBeanServerDelegate]", 
              "addNotificationListener";
    permission javax.management.MBeanPermission 
              "javax.management.MBeanServerDelegate#
              -[JMImplementation:type=MBeanServerDelegate]", 
              "removeNotificationListener";
};

The java.policy file shown in Example 11–7 grants the following permissions:

To Run the RMI Connector Example With Fine-Grained Security

Run this example from within the examplesDir/current/Security/rmi/fine_grained directory.

  1. Compile the example classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java
    
  2. Start an RMI registry on port 9999 of the local host.


    $ export CLASSPATH=server:classpath ; rmiregistry 9999 &
    
  3. Create a java.policy file from the java.policy.template file in the config directory.

    You must replace @INSTALL_HOME_FOR_JDMK@ with your installDir.

  4. Start the Server.

    You need to provide the Server with a pointer to the SSL keystore, the SSL password, the JAAS security manager and the java.policy file when you start the Server class.


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         -Djava.security.manager \
         -Djava.security.policy=config/java.policy Server &
    

    You will see confirmation of the initialization of the environment map, the creation of the MBean server and of the RMI connector.

  5. Start the Client.

    Again, the Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:server:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client
    

    You will see confirmation of the creation of the connector client, the connection to the RMI server and the various MBean operations followed by the closure of the connection.

11.3.2 JMXMP Connectors With Fine-Grained Security

The example of JMXMP connectors with fine-grained security is mostly identical to the example of a simple secure JMXMP connector. The only difference is in the java.policy file used to grant permissions. The java.policy file is in turn mostly identical to the one used in 11.3.1 RMI Connector With Fine-Grained Security, except for the addition of a codebase for SASL, as shown below.


Example 11–8 A java.policy File for a JMXMP Connector With Fine-Grained Security

grant codeBase "file:installDir/lib/jmx.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/jmxremote.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/jmxremote_optional.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/sasl.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/sunsasl.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:installDir/lib/jdmkrt.jar" {
    permission java.security.AllPermission;
};

grant codeBase "file:server" {
    permission java.security.AllPermission;
};

[...]

This java.policy file grants the following permissions:

To Run the JMXMP Connector Example With Fine-Grained Security

Run this example from within the examplesDir/current/Security/jmxmp/fine_grained directory.

  1. Compile the example classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          server/PropertiesFileCallbackHandler.java \
          client/Client.java \
          client/ClientListener.java \
          client/UserPasswordCallbackHandler.java 
    
  2. Create a java.policy file from the java.policy.template file in the config directory.

    You must replace @INSTALL_HOME_FOR_JDMK@ with your installDir.

  3. Start the Server.

    You need to provide the Server with a pointer to the SSL keystore, the SSL password, the JAAS security manager and the java.policy file when you start the Server class.


    $ java -classpath server:mbeans:classpath \
         -Djavax.net.ssl.keyStore=config/keystore \
         -Djavax.net.ssl.keyStorePassword=password \
         -Djava.security.manager \
         -Djava.security.policy=config/java.policy Server &
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  4. Start the Client.

    Again, the Client requires the SSL truststore and its password when it is launched.


    $ java -classpath client:mbeans:classpath \
         -Djavax.net.ssl.trustStore=config/truststore \
         -Djavax.net.ssl.trustStorePassword=trustword \
         Client
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.4 Advanced JMXMP Security Features

In addition to the simple SASL authentication demonstrated in 11.1.2 JMXMP Connectors With Simple Security above, the JMXMP connector can also implement a more complete security solution based on the DIGEST-MD5 SASL mechanism. This allows you to make communication private. These examples also show how to customize your SASL implementation by adding your own version of the SASL provider. A third implementation allows you to supply a custom configuration of the TLS socket factory.

The security features described in this section all relate exclusively to the JMXMP connector.

11.4.1 SASL Privacy

As stated above, Java DMK enables you to make your JMXMP connections private by using a combination of the SASL/DIGEST-MD5 profile, for user authentication and encryption, and file access control based on the MBeanServerForwarder interface, for user access level authorization.

There is an example of an JMXMP connector implementing SASL privacy in the current/Security/jmxmp/sasl_privacy directory in the main examplesDir.


Example 11–9 Implementing SASL Privacy in a JMXMP Connector Server

[...]
import javax.security.sasl.Sasl;

public class Server {

    public static void main(String[] args) {
        try {
            MBeanServer mbs = MBeanServerFactory.createMBeanServer();
            HashMap env = new HashMap();

            Security.addProvider(new com.sun.security.sasl.Provider());
            env.put("jmx.remote.profiles", "SASL/DIGEST-MD5");
            env.put(Sasl.QOP, "auth-conf");
            env.put("jmx.remote.sasl.callback.handler",
		             new DigestMD5ServerCallbackHandler("config" +
						                                    File.separator +
						                                    "password.properties",
						                                    null,
						                                    null));

            env.put("jmx.remote.x.access.file",
           		     "config" + File.separator + "access.properties");

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnectorServer cs =
                JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                                env, 
                                                                mbs);
            cs.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

This example is similar to the simple secure JMXMP example shown in 11.1.2 JMXMP Connectors With Simple Security. It is structured in the same way, with an environment map env being populated with the required security configurations before being passed into a new instance of JMXConnectorServer.

However, as you can see, unlike in the simple secure JMXMP example, which used the PLAIN SASL mechanism, this example uses the DIGEST-MD5 SASL mechanism. The SASL DIGEST-MD5 profile defines the digital signature that must be exchanged between the client and server for a connection to be made, based on the HTTP Digest Authentication mechanism. The HTTP Digest Authentication mechanism was developed by the Internet Engineering Task Force as RFC 2831.

With the protocol profile set to DIGEST-MD5, the level of protection to be provided is defined by the quality of protection property, Sasl.QOP. In this example, the Sasl.QOP is set to auth-conf, denoting that the types of protection to be implemented are authentication and confidentiality, namely encryption. The encryption cypher used is dictated by SASL and can be controlled by properties specific to the DIGEST-MD5 SASL mechanism.

A instance of the callback handler provided with this example, DigestMD5ServerCallbackHandler, is created with the required password file, config/password.properities, passed to it. Similarly, the name of the user allowed to connect to the server, and the level of access granted to that user, are defined when the config/access.properties file is passed into the environment map. These properties must be matched by the client end of the connector, if the connection is to succeed. In this example, the level of access rights granted is readwrite, and the approved user is called username. The password.properties file provides the password for user username that is expected by the SASL/DIGEST-MD5 profile.

The DigestMD5ServerCallbackHandler class, being an implementation of the standard Java callback interface CallbackHandler, allows the server to retrieve the authentication information it needs from the properties files described above. This example does not implement canonical naming or proxy files, so the second and third parameters passed to DigestMD5ServerCallbackHandler when it is instantiated are null. The DIGEST-MD5 server mechanism will then be able to compare them with the user credentials supplied remotely by the client.

The environment map containing all of the above is then used to create the connector server instance, cs.


Example 11–10 Implementing SASL Privacy in a JMXMP Connector Client

public class Client {

    public static void main(String[] args) {
        try {
            HshMap env = new HashMap();

            Security.addProvider(new com.sun.security.sasl.Provider());
            env.put("jmx.remote.profiles", "SASL/DIGEST-MD5");
            env.put(Sasl.QOP, "auth-conf");
            env.put("jmx.remote.sasl.callback.handler",
                    new DigestMD5ClientCallbackHandler("username", 
                                                       "password"));

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnector jmxc = JMXConnectorFactory.connect(url, env);

            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();

            String domains[] = mbsc.getDomains();
            for (int i = 0; i < domains.length; i++) {
                System.out.println("\tDomain[" + i + "] = " + domains[i]);
            }

            ObjectName mbeanName = new ObjectName(
                                        "MBeans:type=SimpleStandard");
            System.out.println("\nCreate SimpleStandard MBean...");
            mbsc.createMBean("SimpleStandard", mbeanName, null, null);

            // Perform MBean operations, before unregistering MBean
            //  & closing connection
            [...]
            mbsc.unregisterMBean(mbeanName);

            jmxc.close();
            System.out.println("\nBye! Bye!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

As shown in Example 11–10 above, the client end of the private connection is created using a very similar set of properties as the server end. However, the client end of the connector supplies the user and password information via a different callback handler, DigestMD5ClientCallbackHandler. Like DigestMD5ServerCallbackHandler, DigestMD5ClientCallbackHandler is an implementation of the CallbackHandler interface. DigestMD5ClientCallbackHandler is passed the username and password expected by DigestMD5ServerCallbackHandler when it is instantiated, which it then passes to the DIGEST-MD5 client mechanism. The client DIGEST-MD5 mechanism then makes them available for comparison with the properties held by the server, thus allowing the connection to the MBean server to be established. Operations can then be performed on the MBeans the MBean server contains.

To Run the Secure JMXMP Connector Example with SASL Privacy

Run this example from within the examplesDir/current/Security/jmxmp/sasl_privacy directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          server/DigestMD5ServerCallbackHandler.java \
          client/Client.java \
          client/ClientListener.java \
          client/DigestMD5ClientCallbackHandler.java 
    
  2. Start the Server.


    $ java -classpath server:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.4.2 SASL Provider

In addition to the simple SASL security and the privacy-based security you can apply to JMXMP connectors, you can also define a custom SASL provider mechanism by defining your own SASL Provider.

The SASL mechanism used in this example is a straightforward customized mechanism called SAMPLE SASL. The example is found in the subdirectories of examplesDir/current/Security/jmxmp/sasl_provider. The SAMPLE SASL mechanism is defined in the SampleServer class, shown below.


Example 11–11 Custom SASL Server

import java.io.*;
import javax.security.sasl.*;

public class SampleServer implements SaslServer {

    private String username;
    private boolean completed = false;

    public String getMechanismName() {
        return "SAMPLE";
    }

    public byte[] evaluateResponse(byte[] responseData) 
            throws SaslException {
        if (completed) {
            throw new SaslException("Already completed");
        }
        completed = true;
        username = new String(responseData);
        return null;
    }

    public String getAuthorizationID() {
        return username;
    }

    public boolean isComplete() {
        return completed;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len) 
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public byte[] wrap(byte[] outgoing, int offset, int len)
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public Object getNegotiatedProperty(String propName) {
        if (propName.equals(Sasl.QOP)) {
            return "auth";
        }
        return null;
    }

    public void dispose() throws SaslException {
    }
}

The custom SASL SAMPLE mechanism defined by the SampleServer class, shown above, extends the SASL interface SaslServer. It simply calls the standard SaslServer methods one by one, to perform the following security operations:


Example 11–12 Custom SASL Server

public class SampleClient implements SaslClient {

    private byte[] username;
    private boolean completed = false;

    public SampleClient(String authorizationID) throws SaslException {
        if (authorizationID != null) {
            try {
                username = ((String)authorizationID).getBytes("UTF8");
            } catch (UnsupportedEncodingException e) {
                throw new SaslException("Cannot convert " + authorizationID
                                        + " into UTF-8", e);
            }
        } else {
            username = new byte[0];
        }
    }

    public String getMechanismName() {
        return "SAMPLE";
    }

    public boolean hasInitialResponse() {
        return true;
    }

    public byte[] evaluateChallenge(byte[] challengeData) 
            throws SaslException {
        if (completed) {
            throw new SaslException("Already completed");
        }
        completed = true;
        return username;
    }

    public boolean isComplete() {
        return completed;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len) 
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public byte[] wrap(byte[] outgoing, int offset, int len)
        throws SaslException {
        throw new SaslException("No negotiated security layer");
    }

    public Object getNegotiatedProperty(String propName) {
        if (propName.equals(Sasl.QOP)) {
            return "auth";
        }
        return null;
    }

    public void dispose() throws SaslException {
    }
}

The SampleClient class is an extension of the SaslClient interface, and calls the standard SaslClient methods one by one, to perform the following operations that are expected by the SampleServer:

With these SASL server and client mechanisms defined, they can then be instantiated by their respective factories.


Example 11–13 A Custom SASL Server Factory

public class ServerFactory implements SaslServerFactory {
   
     public SaslServer createSaslServer(String mechs,
				       String protocol,
				       String serverName,
				       Map props,
				       CallbackHandler cbh)
          throws SaslException {
	         if (mechs.equals("SAMPLE")) {
	             return new SampleServer();
	         }
	         return null;
     }

    public String[] getMechanismNames(Map props) {
	        return new String[]{"SAMPLE"};
    }
}

This basic implementation of the SaslServerFactory interface creates SampleServer instances when it is called with the SASL mechanism parameter set to SAMPLE. None of the other parameters are used by this mechanism.

The SampleClientFactory creates SampleClient instances in exactly the same way as the SampleServerFactory creates SampleServer instances.


Example 11–14 SASL SAMPLE Provider Class

public final class Provider extends java.security.Provider {
    public Provider() {
	super("SampleSasl", 1.0, "SAMPLE SASL MECHANISM PROVIDER");
	put("SaslClientFactory.SAMPLE", "ClientFactory");
	put("SaslServerFactory.SAMPLE", "ServerFactory");
    }
}

The SASL SAMPLE Provider constructor shown above specifies the name of the provider (SampleSasl), the version of this provider (in this case, 1.0) and a description of the provider. It then defines the server and client factories that are used to create SASL servers and clients implementing this SASL mechanism.

With the mechanisms above thus defined, all the Server and Client classes used in this example require the correct Provider to be installed in the environment.


Example 11–15 Adding a Provider to a JMX Connector Server

public class Server {

    public static void main(String[] args) {
        try {
            MBeanServer mbs = MBeanServerFactory.createMBeanServer();
            HashMap env = new HashMap();

            Security.addProvider(new Provider());
            env.put("jmx.remote.profiles", "SASL/SAMPLE");
            env.put("jmx.remote.x.access.file",
      		    "config" + File.separator + "access.properties");

            JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555);
            JMXConnectorServer cs =
                JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                                env, 
                                                                mbs);

            cs.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The SAMPLE SASL mechanism is passed into the Client in the same way.

To Run the Secure JMXMP Connector Example with a SASL Provider

Run this example from within the examplesDir/current/Security/jmxmp/sasl_provider directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java \
          sample/Provider.java \
          sample/ClientFactory.java \
          sample/ServerFactory.java \
          sample/SampleClient.java \
          sample/SampleServer.java 
    
  2. Start the Server.


    $ java -classpath server:sample:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:sample:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.

11.4.3 TLS Socket Factory

Your JMXMP connections can also be secured using an implementation of Transport Layer Security (TLS) sockets, as shown in the following example. This example is taken from the sub-directories of examplesDir/current/Security/jmxmp/tls_factory. The example shows how to provide a custom configured TLS factory for use by the client and the server.


Example 11–16 Securing a JMXMP Connector Server Using TLS Socket Factories

public class Server { 
 
    public static void main(String[] args) { 
      try { 
           MBeanServer mbs = MBeanServerFactory.createMBeanServer(); 
           HashMap env = new HashMap(); 
           String keystore = "config" + File.separator + "keystore"; 
           char keystorepass[] = "password".toCharArray(); 
           char keypassword[] = "password".toCharArray(); 
           KeyStore ks = KeyStore.getInstance("JKS"); 
           ks.load(new FileInputStream(keystore), keystorepass); 
           KeyManagerFactory kmf = 
              KeyManagerFactory.getInstance("SunX509"); 
           kmf.init(ks, keypassword); 
           SSLContext ctx = SSLContext.getInstance("TLSv1"); 
           ctx.init(kmf.getKeyManagers(), null, null); 
           SSLSocketFactory ssf = ctx.getSocketFactory(); 
           env.put("jmx.remote.profiles", "TLS"); 
           env.put("jmx.remote.tls.socket.factory", ssf); 
           env.put("jmx.remote.tls.enabled.protocols", "TLSv1"); 
           env.put("jmx.remote.tls.enabled.cipher.suites", 
                 "SSL_RSA_WITH_NULL_MD5"); 
 
           JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
           JMXConnectorServer cs = 
              JMXConnectorServerFactory.newJMXConnectorServer(url, 
                                                              env, 
                                                              mbs); 
           cs.start(); 
 
         } catch (Exception e) { 
           e.printStackTrace(); 
         } 
      } 
  } 

Example 11–16 shows the creation of an MBean server mbs, an environment map env, and a key store object ks. The key store object ks is initialized by loading in the input stream keystore and the key store password, keystorepass, that grants access to the key store.

An instance of the SSL class KeyManagerFactory, named kmf, that implements the SunX509 key management algorithm, is then initialized. The kmf instance is passed the key information contained in the key store ks and the key password keypassword that grants access to the keys in the key store.

The SSL context, ctx, is set to use the protocol version TLSv1, and is initialized with kmf as its key manager. Finally, an instance of SSLSocketFactory, named ssf, is created by calling the getSocketFactory() method of the SSL context ctx.

The environment map env is populated with the TLS profiles, the SSL socket factory ssf, the TLSv1 protocol version, and the SSL_RSA_WITH_NULL_MD5 cipher suite.

Finally, with an instance of JMXConnectorServer, named cs, is created. The connector server cs is started by a call to the start() method of JMXConnectorServer.


Example 11–17 Securing a JMXMP Connector Client Using TLS Socket Factories

public class Client { 
 
   public static void main(String[] args) { 
     try { 
          HashMap env = new HashMap(); 
          String truststore = "config" + File.separator + "truststore"; 
          char truststorepass[] = "trustword".toCharArray(); 
          KeyStore ks = KeyStore.getInstance("JKS"); 
          ks.load(new FileInputStream(truststore), truststorepass); 
          TrustManagerFactory tmf =  
             TrustManagerFactory.getInstance("SunX509"); 
          tmf.init(ks); 
          SSLContext ctx = SSLContext.getInstance("TLSv1"); 
          ctx.init(null, tmf.getTrustManagers(), null); 
          SSLSocketFactory ssf = ctx.getSocketFactory(); 
          env.put("jmx.remote.profiles", "TLS"); 
          env.put("jmx.remote.tls.socket.factory", ssf); 
          env.put("jmx.remote.tls.enabled.protocols", "TLSv1"); 
          env.put("jmx.remote.tls.enabled.cipher.suites", 
             "SSL_RSA_WITH_NULL_MD5"); 
 
          JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
          JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
          MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); 
          String domains[] = mbsc.getDomains(); 
          for (int i = 0; i < domains.length; i++) { 
          System.out.println("Domain[" + i + "] = " + domains[i]); 
          } 
 
          ObjectName mbeanName = 
              new ObjectName("MBeans:type=SimpleStandard"); 
          mbsc.createMBean("SimpleStandard", mbeanName, null, null); 
          // Perform MBean operations 
          // 
          [...] 
          mbsc.removeNotificationListener(mbeanName, listener); 
          mbsc.unregisterMBean(mbeanName); 
          jmxc.close(); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
     } 
} 

As for the Server, Example 11–17 shows the creation of an environment map env, and a key store object ks for the JMXMP connector Client. The key store object ks is initialized by loading in the input stream truststore and the password, truststorepass, that grants access to the trust store.

An instance of the SSL class TrustManagerFactory, named tmf, that implements the SunX509 key management algorithm, is then initialized. The tmf instance is passed the key information contained in the key store ks.

The SSL context, ctx, is set to use the protocol version TLSv1, and is initialized with tmf as its trust manager. Finally, an instance of SSLSocketFactory, named ssf, is created by calling the getSocketFactory() method of the SSL context ctx.

The environment map env is populated with the the SSL socket factory ssf, the TLSv1 protocol version, and the SSL_RSA_WITH_NULL_MD5 cipher suite.

An instance of JMXConnector, named jmxc is then created, and an MBean server connection, mbsc, is made by calling the getMBeanServerConnection method of the JMX Remote API connector jmxc.

In code that is not shown here, when the connection to the MBean server has been established, the Client creates an MBean called SimpleStandard and performs various operations on it. Once these MBean operations have completed, the Client unregisters the MBean, and closes down the connection jmxc.

To Run the Secure JMXMP Connector Example with TLS Socket Factories

Run this example from within the examplesDir/current/Security/jmxmp/tls_factory directory.

  1. Compile the Java classes.


    $ javac -classpath classpath \
          mbeans/SimpleStandard.java \
          mbeans/SimpleStandardMBean.java \
          server/Server.java \
          client/Client.java \
          client/ClientListener.java 
    
  2. Start the Server.


    $ java -classpath server:mbeans:classpath Server & 
    

    You will see confirmation of the creation of the MBean server, the initialization of the environment map and the launching of the JMXMP connector and its registration in the MBean server.

  3. Start the Client.


    $ java -classpath client:mbeans:classpath Client 
    

    You will see confirmation of the creation of the JMXMP connector client, the initialization of the environment map, the connection to the MBean server and the performance of the various MBean operations followed by the closure of the connection.