I am trying to run a JMX application secured by SSL and passwords and accessed by jconsole. According to documentation it is achieved by setting system properties. OK I got it to work designeating an SSL port and configuring certificate stores on both application and jconsole client.
Authentication and discovery probably are using my SSL port but there are additional connections that are active later on using different ports. I suppose they are JMX-RMI connections - but I dont know if they are using SSL. Additionally I cannot use arbitrarily assigned ports because of firewalling. I would expect that RMI should work over the existing SSL conection.
I dont want to (can not) program RMI directly but would prefer to declare it via appropriate JMX configuration parameters. Also I want to use the jconsole as JMX client and not code up my own.
Unfortunately you won't be able to export the JVM's RMIServer and RMIConnection
remote objects on a given port.
"com.sun.management.jmxremote.port" in the management.properties file applies to
the port where the RMIRegistry will be reachable. Moreover, the JVM's agent cannot
be configured with custom RMI socket factories. Through the management.properties
file you can just configure the standard SSL RMI socket factories but you cannot supply
a port for the RMIServer and RMIConnection remote objects to be exported.
However, you could create your own RMIConnectorServer supplying the platform
MBeanServer as input by calling the ManagementFactory.getPlatformMBeanServer()
static method and tell the server in which port remote objects should be exported. In
order to do this you don't need to supply custom RMI socket factories (and still use the
standard SSL RMI socket factories) but just use the following JMXServiceURL:
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi://localhost:3000/jndi/rmi://localhost:9000/server");
which tells the RMIConnectorServer to bind to the RMIRegistry running on port 9000
and to export the RMIServer and RMIConnection remote objects on port 3000.
The following server's code snippet allows to mimic the functionality implemented by
the out-of-the-box management agent:
...
// Start rmi registry
//
System.out.println("Create the RMI registry on port 9000");
java.rmi.registry.LocateRegistry.createRegistry(9000);
// Instantiate the MBean server
//
System.out.println("Get the platform's MBean server");
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Environment map
//
System.out.println("Initialize the environment map");
HashMap env = new HashMap();
// Provide SSL-based RMI socket factories.
//
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);
// Provide the password file used by the connector server to
// perform user authentication. The password file is a properties
// based text file specifying username/password pairs. This
// properties based password authenticator has been implemented
// using the JMXAuthenticator interface and is passed to the
// connector through the "jmx.remote.authenticator" property
// in the map.
//
// This property is implementation-dependent and might not be
// supported by all implementations of the JMX Remote API.
//
env.put("jmx.remote.x.password.file", "password.properties");
// Provide the access level file used by the connector server to
// perform user authorization. The access level file is a properties
// based text file specifying username/access level pairs where
// access level is either "readonly" or "readwrite" access to the
// MBeanServer operations. This properties based access control
// checker has been implemented using the MBeanServerForwarder
// interface which wraps the real MBean server inside an access
// controller MBean server which performs the access control checks
// before forwarding the requests to the real MBean server.
//
// This property is implementation-dependent and might not be
// supported by all implementations of the JMX Remote API.
//
env.put("jmx.remote.x.access.file", "access.properties");
// Create an RMI connector server
//
System.out.println("Create an RMI connector server");
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi://localhost:3000/jndi/rmi://localhost:9000/server");
JMXConnectorServer cs =
JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
// Start the RMI connector server
//
System.out.println("Start the RMI connector server");
cs.start();
...
Concerning the SSL interactions between the client (JConsole) and the server, all the
remote method invocations over the RMIServer and RMIConnection remote objects
are performed through SSL. Only the retrieval of the stubs from the RMIRegistry is not
performed through SSL but through a plain socket. In the out-of-the-box management
agent you cannot use SSL-protected RMI registries. This limitation has been fixed in
the out-of-the-box management agent that ships with Java SE 6 (Mustang) by means
of a new property "com.sun.management.jmxremote.registry.ssl=true" in the
management.properties file. Mustang's JConsole has also been updated to
be able to fetch the RMIServer stubs from an SSL-protected RMIRegistry.
Regards,
Luis-Miguel ALVENTOSA
JMX - Java SE development team
Sun Microsystems, Inc.
where the jmxc is registered into my MBeanServer. Your demonstration of the use of the URL specifying ports is very helpful. Also I learned how to run rmiregistry in the same JVM.
Thanks again - I got my project going thanks to your help and pretty much using your example code.
It might be helpful to add what system props should be defined. It seems it is necessary to have and what not to have:
#turn this off - wrapper.java.additional.1=-Dcom.sun.management.jmxremote.port=5100
wrapper.java.additional.1=-Dcom.sun.management.jmxremote
wrapper.java.additional.2=-Dcom.sun.management.jmxremote.authenticate=true
wrapper.java.additional.3=-Dcom.sun.management.jmxremote.ssl=true
wrapper.java.additional.4=-Dcom.sun.management.jmxremote.ssl.need.client.auth=false
Maybe some of them are defaults but I have them set up for clarity. The jmxremote.port should not be set because otherwise JMX sets up another connector.
What is also needed are the standard SSL keystore parameters:
I tried the same example, but it did not work for me. I used the code snippet given above and also the properies. When I invoke the jconsole and click on the local TAB, I do not see the process ID of the agent. I do see the agent PID when I am using out-of-the-box agent. I also clicked on remote TAB to see if I can connect using the username and password.
I am using jdk1.5.0_06 for both agent and jconsole
My main goal is to make the agent work when a firewall is involved and the stubs to be exported to a known port.
I got it working by commenting the following lines in my code:
//env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
//env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
Thanks for the above posts that helped me in resolving the problem.
Does the above fix mean that I still have to open an additional [known] port on the firewall, or can I make use of SSH tunneling and then connect jconsole to the tunneled port on my localhost without getting involved with security issues and opening additional ports on the corporate firewall?
As you can see I made the 3000 and 9000 port numbers configurable, but in the end realized that it is not necessary, because when I use other ports, my application can't start (Connection refused exceptions).
Also note that I had to add this VM arg:
-Djava.rmi.server.hostname=localhost \
as I need to force the data port (which is in fact tunnelled to a local port on my localhost) to be looked up on localhost. If I don't add this, the JDK automatically resolves the RMI server's host name to be the internal IP of the behind-the-firewall-remote-machine.
I also noticed that the tunnelled ports on my localhost have to match the 3000 and 9000 of the remote application, otherwise it doesn't work.
When opening JConsole, I go to the Advanced tab, and complete following information:
JMX URL: service:jmx:rmi://localhost:3000/jndi/rmi://localhost:9000/server
User Name: username configured in password.properties
Password: password configured in password.properties
Also just make sure that you have configured the access levels (readonly, readwrite) correctly for the users in password.properties.
The -Dcom.sun.management.jmxremote* system properties are not needed as you are starting your own RMI connector server and not the RMI connector server used by the out-of-the-box management agent.