componative - monitor your applications with jconsole - part 3

14
Componative About Developer Services Developer Insights Monitor Your Applications With JConsole - Part 3 Tuesday, June 5, 2007 Using JConsole in a production environment usually involves having to go through a firewall. Because JConsole uses RMI to communicate with remote applications, we need to open up the firewall or use some kind of tunnel. Both scenarios require a bit of customization of the remote application to make this possible as we'll see in this last part. In Part 2 we showed how to use JConsole to monitor and manage JBoss running on a remote machine that's not behind a firewall. In the final scenario that we're going to look at we'll see what's needed to access JBoss with JConsole through a firewall. When dealing with a firewall located between the machine running JConsole and the machine running the remote application (e.g. JBoss) there are basically two options: Opening ports in the firewall needed for the RMI communication between JConsole and the remote application. 1. Tunneling the RMI communication over a port that's already open. 2. The first option is obviously the simpler of the two, but requires the cooperation of your friendly firewall administrator. While this may prove to be difficult depending on how friendly your firewall administrator really is, don't dismiss this option as impossible right away. Don't forget that even if you decide to use tunneling you still have to talk to your firewall administrator to make sure he approves of this solution and to check if it's allowed within the security policies of your company. It's tempting to think of tunneling as a way to not have to deal with your firewall administrator or even as a method to circumvent security measures, but this may get you in some serious trouble in the long run. So the best way to deal with a firewall is to get in touch with your firewall administrator and explain to him why you need JConsole and what you intend to do. Ask him if he's willing (and allowed) to open ports for you and what kind of procedure you'll have to follow to realize this. Only if it's absolutely not possible to open ports, should you propose to use tunneling. Don't give up too easy if the first response to your request to open ports is negative, since usually firewall administrators will favor this option above tunneling. The reason for this is that opening ports gives firewall administrators more control because this gives them the ability to only allow access to these ports from specific IP-addresses or ranges, while tunneling opens up the firewall for everyone who has access to the port you're tunneling over. Let's take a look at what we have to do after we managed to convince our firewall administrator to open up ports in the firewall. We just have to negotiate about which port to use and then configure it using the com.sun.management.jmxremote.port system property, right? Unfortunately, things are a bit more complicated. First of all we need two ports for communicating with a remote application using RMI. The first port, which we can configure with the system property mentioned above, is used by the RMI Registry. The second port is used by the JMX Connector Server of the JMX Agent. JConsole will access the RMI Registry to find out where it can find the JMX Connector Server to communicate with the MBean server. The problem is that we can't configure the port that the JMX Connector Server should use, because this is chosen by the RMI stack. We can look at the detailed logging that we enabled for JConsole to find out which port is being used to connect to the remote application. Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in... 1 de 14 2/11/11 3:50 AM

Upload: raphael-bertozzi

Post on 02-Apr-2015

83 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Componative - Monitor Your Applications With JConsole - Part 3

ComponativeAboutDeveloperServices

DeveloperInsights Monitor Your Applications With JConsole - Part 3

Tuesday, June 5, 2007

Using JConsole in a production environment usually involves having to gothrough a firewall. Because JConsole uses RMI to communicate with remoteapplications, we need to open up the firewall or use some kind of tunnel.Both scenarios require a bit of customization of the remote application tomake this possible as we'll see in this last part.

In Part 2 we showed how to use JConsole to monitor and manage JBoss running on aremote machine that's not behind a firewall. In the final scenario that we're going tolook at we'll see what's needed to access JBoss with JConsole through a firewall. Whendealing with a firewall located between the machine running JConsole and the machinerunning the remote application (e.g. JBoss) there are basically two options:

Opening ports in the firewall needed for the RMI communication betweenJConsole and the remote application.

1.

Tunneling the RMI communication over a port that's already open.2.

The first option is obviously the simpler of the two, but requires the cooperation ofyour friendly firewall administrator. While this may prove to be difficult depending onhow friendly your firewall administrator really is, don't dismiss this option asimpossible right away. Don't forget that even if you decide to use tunneling you stillhave to talk to your firewall administrator to make sure he approves of this solutionand to check if it's allowed within the security policies of your company. It's temptingto think of tunneling as a way to not have to deal with your firewall administrator oreven as a method to circumvent security measures, but this may get you in someserious trouble in the long run.

So the best way to deal with a firewall is to get in touch with your firewalladministrator and explain to him why you need JConsole and what you intend to do.Ask him if he's willing (and allowed) to open ports for you and what kind of procedureyou'll have to follow to realize this. Only if it's absolutely not possible to open ports,should you propose to use tunneling. Don't give up too easy if the first response toyour request to open ports is negative, since usually firewall administrators will favorthis option above tunneling. The reason for this is that opening ports gives firewalladministrators more control because this gives them the ability to only allow access tothese ports from specific IP-addresses or ranges, while tunneling opens up the firewallfor everyone who has access to the port you're tunneling over.

Let's take a look at what we have to do after we managed to convince our firewalladministrator to open up ports in the firewall. We just have to negotiate about whichport to use and then configure it using the com.sun.management.jmxremote.portsystem property, right? Unfortunately, things are a bit more complicated. First of allwe need two ports for communicating with a remote application using RMI. The firstport, which we can configure with the system property mentioned above, is used bythe RMI Registry. The second port is used by the JMX Connector Server of the JMXAgent. JConsole will access the RMI Registry to find out where it can find the JMXConnector Server to communicate with the MBean server.

The problem is that we can't configure the port that the JMX Connector Server shoulduse, because this is chosen by the RMI stack. We can look at the detailed logging thatwe enabled for JConsole to find out which port is being used to connect to the remoteapplication.

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

1 de 14 2/11/11 3:50 AM

Page 2: Componative - Monitor Your Applications With JConsole - Part 3

However there's no guarantee that the JMX Connector Server will always be availableon the same port. Most likely after restarting JBoss the RMI stack will choose anotherport. So before we can tell our firewall administrator which ports need to be opened,we have got to find a way to make the JMX Connector Server always available on thesame port. Luckily we can use the JMX Remote API to programmatically create theJMX Connector Server and control the port it uses. All we really have to do is create aJMXServiceURL using the ports we want to use and create and start the JMXConnector Server.

The code sample below shows the most basic setup for creating a custom JMXConnector Server that can be used by JConsole to communicate with the MBeanserver.

MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

JMXServiceURL url = new JMXServiceURL( "service:jmx:rmi://localhost:12199/jndi/rmi://localhost:11199/jmxrmi");

JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); connectorServer.start();

The JMXServiceURL above specifies that the JMX Connector Server will use port12199 and register itself with the RMI registry running on port 11199. Of course wealso have to make sure the RMI registry is running and we need some extra code if wewant to use SSL and authentication, but before we do that let's first think about wherewe're going to put this code.

One option is to add this code to our application running in JBoss and make sure it'sexecuted during startup of the application. We could do this very easily by calling thiscode during the initialization of a servlet that's configured to load on startup. Althoughthis will work just fine, it's better not to incorporate this functionality in ourapplication. First of all when we're using JConsole we're not just monitoring andmanaging our application, but JBoss and all the applications running on it. Sotechnically this functionality should not be part of any single application, but bepackaged on its own. This will also make it easier to reuse our custom JMX ConnectorServer.

We could create a very simple application containing just a servlet to execute the codeto create the JMX Connector Server during startup and deploy it alongside our otherapplication(s). But there's an even better way to do this. We can create aServiceMBean to create a custom JBoss service, which will also be available in theJBoss JMX console so we can change the configuration and start and stop the service.

Creating the JConsoleService MBean for JBoss

To create a ServiceMBean we need three things: the MBean interface, the MBeanimplementation and a jboss-service.xml located in the META-INF folder. Bypackaging these files as a Service Archive (.sar) file and dropping it in the JBossdeploy folder our ServiceMBean will be deployed and started automatically. Since a

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

2 de 14 2/11/11 3:50 AM

Page 3: Componative - Monitor Your Applications With JConsole - Part 3

.sar file is nothing more than a JAR file with a different extension creating it is verysimple. For our JConsoleService the content of the .sar file will look something likethis when we list it using the jar utility:

META-INF/MANIFEST.MF com/componative/jboss/services/jconsole/JConsoleService.class com/componative/jboss/services/jconsole/JConsoleServiceMBean.class META-INF/jboss-service.xml

Let's start with creating the MBean interface. Because we'll be using a Standard MBeanfor our service the interface must take the name of the Java class that implements itwith the suffix MBean added (see Resources for more information). We'll useJConsoleService for the implementation class, giving us the nameJConsoleServiceMBean for the interface.By extending the interface from org.jboss.system.ServiceMBean JBoss willautomatically call the JBoss specific lifecycle methods create() and start() duringstartup and stop() and destroy() during shutdown (see Resources for moreinformation). This will also enable us to call these methods from the JBoss JMXConsole. For now we will not define any attributes or extra operations, so the interfacewill simply look like this:

public interface JConsoleServiceMBean extends ServiceMBean { }

For the implementation of the MBean we create a JConsoleService class that extendsfrom org.jboss.system.ServiceMBeanSupport. This base class will take care ofimplementing the ServiceMBean interface, leaving us with only three methods thatneed to be overridden:

getName() for returning the name of our service as displayed in the JBoss JMXConsole.startService() for starting our service.stopService() for stopping our service.

We could also override the createService() and destroyService() methods, but wedon't need them for this service. This gives us our first simple version of theJConsoleService. Don't worry about the hardcoded port numbers for now; we'll fixthis shortly.

public class JConsoleService extends ServiceMBeanSupport implements JConsoleServiceMBean { private JMXConnectorServer connectorServer = null; private Registry rmiRegistry = null; @Override public String getName() { return "com.componative.jboss:service=JConsoleService"; } @Override protected void startService() throws Exception { getLog().info("Starting JConsoleService..."); super.startService();

MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

JMXServiceURL url = new JMXServiceURL( "service:jmx:rmi://localhost:12199/jndi/rmi://localhost:11199/jmxrmi");

startRMIRegistry(); this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); this.connectorServer.start(); }

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

3 de 14 2/11/11 3:50 AM

Page 4: Componative - Monitor Your Applications With JConsole - Part 3

@Override protected void stopService() throws Exception { getLog().info("Stopping JConsoleService...");

if (this.connectorServer != null) { this.connectorServer.stop(); this.connectorServer = null; }

stopRMIRegistry(); super.stopService(); } private void startRMIRegistry() throws RemoteException { this.rmiRegistry = null; try { // Ensure cryptographically strong random number generator used // to choose the object number - see java.rmi.server.ObjID System.setProperty("java.rmi.server.randomIDs", "true"); this.rmiRegistry = LocateRegistry.createRegistry(11199); } catch (ExportException ex) { // registry already exists // => no problem, just try to use it // => don't set rmiRegistry instance variable so we don't stop it // during stopService() since it may be used for other remote // objects // => log a message to indicate we're using an existing registry getLog().warn("RMIRegistry already exists: " + ex.toString()); } } private void stopRMIRegistry() throws NoSuchObjectException { if (this.rmiRegistry != null) { UnicastRemoteObject.unexportObject(this.rmiRegistry, true); this.rmiRegistry = null; } } }

As we can see the code for creating and starting the JMX Connector Server ended upin the startService() method and we added functionality to stop the JMX ConnectorServer to the stopService() method. But what about the startRMIRegistry()method? Couldn't we simply use the default RMI registry running on port 1099 or startone using the out-of-the-box configuration we used in Part 2? The answer to bothquestions is: no we can't.

The reason why we can't use the default RMI registry port 1099, is because JBoss usesit to run it's JNDI service instead of a RMI registry. If we would try to register our JMXConnector Server with the JNDI service running on port 1099 we would get anjava.rmi.ConnectIOException telling us that were trying to connect to a 'non-JRMPserver at remote endpoint'.Using -Dcom.sun.management.jmxremote.port=11199 on the other hand will create anon-modifiable RMI registry containing the out-of-the-box JMX Connector Server. If wetry to replace this one with our custom version we'll get the following exception:

java.rmi.AccessException: Cannot modify this registry

So we simply have to start (and stop) the registry ourselves. The only tricky part hereis not to stop the registry if it was already running. The problem is that we can't test ifthe registry is running by simply calling LocateRegistry.getRegistry(port)because this will always return a reference even if the registry is not running as wecan read in the JavaDoc for LocateRegistry:

Note that a getRegistry call does not actually make a connection to theremote host. It simply creates a local reference to the remote registry andwill succeed even if no registry is running on the remote host. Therefore, asubsequent method invocation to a remote registry returned as a result ofthis method may fail.

This leaves us with two options: use LocateRegistry.getRegistry(port) and call

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

4 de 14 2/11/11 3:50 AM

Page 5: Componative - Monitor Your Applications With JConsole - Part 3

for example the list() method on the returned registry to see if it generates anexception or simply try to create the registry and catch the exception that is thrown ifit already exits. Since the most common scenario will be that there is no registryrunning yet, we use the second option here because this will generally result in lessexceptions being thrown and caught. As a bonus this also requires less code.

Finally we need a jboss-service.xml to tell JBoss which class implements the serviceand the name of the service.

<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="com.componative.jboss.services.jconsole.JConsoleService" name="com.componative.jboss:service=JConsoleService"> </mbean> </server>

Now we can package these three files as a .sar file and drop it in the JBoss deployfolder (see Resources for downloads of the .sar file). Before you do this make surethat the port configured for the out-of-the-box version of the JMX Connector Serverdoes not conflict with our custom version. If this is the case you can run both theout-of-the-box and the custom version simultaneously as long as you remember thatour custom version will not use any of the system properties used by the out-of-the-box version except for the keystore and truststore settings as we'll see later on. Toavoid confusing it's probably better to remove the out-of-the-box settings from theJBoss startup script.

If we now start JConsole and connect to JBoss on port 11199 we can see in thedetailed logging that port 12199 is used for the JMX Connector Server.

Adding features to the JConsoleService

Now that we've got our first version going, let's add the same features the out-of-the-box version has and make the settings configurable via the JBoss JMX Console. Westart with adding the getters and setters for the different attributes to the MBeaninterface. We'll also add a readonly attribute to determine if SSL client authenticationfor the RMI registry is supported (i.e. if we're running on JDK 6 or later).

public interface JConsoleServiceMBean extends ServiceMBean { void setRMIRegistryPort(int port); int getRMIRegistryPort();

void setJMXConnectorServerPort(int port); int getJMXConnectorServerPort();

void setPasswordFile(String passwordFile); String getPasswordFile();

void setAccessFile(String accessFile); String getAccessFile();

void setSSLEnabled(boolean enabled); boolean isSSLEnabled();

void setSSLClientAuthenticationEnabled(boolean enabled);

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

5 de 14 2/11/11 3:50 AM

Page 6: Componative - Monitor Your Applications With JConsole - Part 3

boolean isSSLClientAuthenticationEnabled();

void setSSLRegistryAuthenticationEnabled(boolean enabled); boolean isSSLRegistryAuthenticationEnabled();

boolean isSSLRegistryAuthenticationSupported(); }

Next we implement the getters and setters defined on the interface.

public class JConsoleService extends ServiceMBeanSupport implements JConsoleServiceMBean { private int rmiRegistryPort = 0; private int jmxConnectorServerPort = 0; private String passwordFile = null; private String accessFile = null; private boolean sslEnabled = false; private boolean sslClientAuthenticationEnabled = false; private boolean sslRegistryAuthenticationEnabled = false; ...

public void setRMIRegistryPort(int port) { this.rmiRegistryPort = port; }

public int getRMIRegistryPort() { return this.rmiRegistryPort; }

public void setJMXConnectorServerPort(int port) { this.jmxConnectorServerPort = port; }

public int getJMXConnectorServerPort() { return this.jmxConnectorServerPort; }

public void setAccessFile(String accessFile) { this.accessFile = accessFile; }

public String getAccessFile() { return this.accessFile; }

public void setPasswordFile(String passwordFile) { this.passwordFile = passwordFile; }

public String getPasswordFile() { return this.passwordFile; }

public void setSSLEnabled(boolean enabled) { this.sslEnabled = enabled; }

public boolean isSSLEnabled() { return this.sslEnabled; }

public void setSSLClientAuthenticationEnabled(boolean enabled) { this.sslClientAuthenticationEnabled = enabled; }

public boolean isSSLClientAuthenticationEnabled() { // client authentication is only possible if SSL is enabled if (!isSSLEnabled()) { this.sslClientAuthenticationEnabled = false; }

return this.sslClientAuthenticationEnabled; }

public void setSSLRegistryAuthenticationEnabled(boolean enabled) { this.sslRegistryAuthenticationEnabled = enabled; }

public boolean isSSLRegistryAuthenticationEnabled() { // registry authentication is only possible if client authentication is

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

6 de 14 2/11/11 3:50 AM

Page 7: Componative - Monitor Your Applications With JConsole - Part 3

// enabled and the feature is supported if (!isSSLEnabled() || !isSSLClientAuthenticationEnabled() || !isSSLRegistryAuthenticationSupported()) { this.sslRegistryAuthenticationEnabled = false; } return this.sslRegistryAuthenticationEnabled; }

public boolean isSSLRegistryAuthenticationSupported() { boolean supported = false; String version = System.getProperty("java.version"); if (version != null && version.length() >= 3) { supported = version.substring(0, 3).compareTo("1.5") > 0; } return supported; } ... }

The implementation will check if the prerequisites for enabling a feature are met andautomatically disable the feature if this is not the case. For example if SSL is disabledSSL client authentication will automatically be disabled as well. These checks areperformed in the getters of the attributes instead of the setters for two reasons. TheJBoss JMX console can call the setters in 'random' order when saving theconfiguration, which could lead to unwanted behaviour if the user changes multiplefeatures at once. By moving the checks to the getters, the setters for the attributesdon't need knowledge about the prerequisites of other attributes.

With these attributes added we can now change the startService() method to usethese attributes.

@Override protected void startService() throws Exception { getLog().info("Starting JConsoleService..."); super.startService();

MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost:" + getJMXConnectorServerPort() + "/jndi/rmi://localhost:" + getRMIRegistryPort() + "/jmxrmi");

Map<String,Object> env = createJMXConnectorServerEnvironment();

startRMIRegistry( (RMIClientSocketFactory)env.get( RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE), (RMIServerSocketFactory)env.get( RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE)); this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbeanServer);

this.connectorServer.start(); }

The most interesting changes are the use of environment settings for the creation ofthe JMX Connector Server and the use of socket factories for the RMI registry. Theenvironment settings contain the features that we want for our custom JMX ConnectorServer as we can see in the method used to create them.

private static final String NO_FILE = "-"; private Map<String,Object> createJMXConnectorServerEnvironment() { Map<String,Object> env = new HashMap<String,Object>();

// set password and access file if (!JConsoleService.NO_FILE.equals(getPasswordFile())) { env.put("jmx.remote.x.password.file", getPasswordFile());

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

7 de 14 2/11/11 3:50 AM

Page 8: Componative - Monitor Your Applications With JConsole - Part 3

} if (!JConsoleService.NO_FILE.equals(getAccessFile())) { env.put("jmx.remote.x.access.file", getAccessFile()); }

RMIClientSocketFactory csf = null; RMIServerSocketFactory ssf = null; if (isSSLEnabled()) { csf = new SslRMIClientSocketFactory(); ssf = new SslRMIServerSocketFactory( null, null, isSSLClientAuthenticationEnabled()); env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);

if (isSSLRegistryAuthenticationEnabled()) { env.put("com.sun.jndi.rmi.factory.socket", csf); } }

return env; }

The changes to the startRMIRegistry() method involve using the socket factories ifSSL client authentication is enabled for the registry and replacing the hardcoded portnumber with getRMIRegistryPort().

private void startRMIRegistry( RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory) throws RemoteException {

this.rmiRegistry = null; try { // Ensure cryptographically strong random number generator used // to choose the object number - see java.rmi.server.ObjID System.setProperty("java.rmi.server.randomIDs", "true"); if (isSSLRegistryAuthenticationEnabled()) { this.rmiRegistry = LocateRegistry.createRegistry(getRMIRegistryPort(), clientSocketFactory, serverSocketFactory); } else { this.rmiRegistry = LocateRegistry.createRegistry(getRMIRegistryPort()); } } catch (ExportException ex) { // registry already exists // => no problem, just try to use it // => don't set rmiRegistry instance variable so we don't stop it // during stopService() since it may be used for other remote // objects // => log a message to indicate we're using an existing registry getLog().warn("RMIRegistry already exists: " + ex.toString()); } }

The final thing to do is set the default configuration for the service by adding aconstructor for the MBean and configuring the parameters for it in the jboss-service.xml file.

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

8 de 14 2/11/11 3:50 AM

Page 9: Componative - Monitor Your Applications With JConsole - Part 3

public JConsoleService( int rmiRegistryPort, int jmxConnectorServerPort, String passwordFile, String accessFile, boolean sslEnabled, boolean sslClientAuthenticationEnabled, boolean sslRegistryAuthenticationEnabled) {

super(); setRMIRegistryPort(rmiRegistryPort); setJMXConnectorServerPort(jmxConnectorServerPort); setPasswordFile(passwordFile); setAccessFile(accessFile); setSSLEnabled(sslEnabled); setSSLClientAuthenticationEnabled(sslClientAuthenticationEnabled); setSSLRegistryAuthenticationEnabled(sslRegistryAuthenticationEnabled); }

<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="com.componative.jboss.services.jconsole.JConsoleService" name="com.componative.jboss:service=JConsoleService">

<constructor> <!-- RMIRegistry port --> <arg type="int" value="11199"/> <!-- JMXConnectorServer port --> <arg type="int" value="12199"/> <!-- password file --> <arg type="java.lang.String" value="-"/> <!-- access file --> <arg type="java.lang.String" value="-"/> <!-- enable SSL --> <arg type="boolean" value="false"/> <!-- enable SSL client authentication --> <arg type="boolean" value="false"/> <!-- enable SSL client authentication for registry --> <arg type="boolean" value="false"/> </constructor> </mbean> </server>

If we now deploy the .sar file and go to the JConsoleService in the JBoss JMX consolewe can see all the attributes of the MBean that we can configure.

After applying a change we can scroll down and use the stop and start operations toactivate the new configuration. When changing the configuration there are a fewthings to keep in mind.

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

9 de 14 2/11/11 3:50 AM

Page 10: Componative - Monitor Your Applications With JConsole - Part 3

If we want to enable the SSL features we need to add the keystore and the truststore(for client authentication) settings in the JBoss startup script just like we did in Part 2.As a quick reminder, the system properties to use are:

javax.net.ssl.keyStorejavax.net.ssl.keyStorePasswordjavax.net.ssl.trustStore

Changes made to the configuration will be lost after a restart of JBoss. To permanentlychange the configuration modify the settings in the jboss-service.xml and redeploythe .sar file. Finally it's important to note that the JMX console ignores exceptionsbeing thrown by operations of ServiceMBeans, so we need to check the log file to seeif the service was started successfully.

Adding tunneling functionality

Using the JConsoleService we can now use the same features offered by the out-of-the-box version of the JMX Connector Service using fixed ports so we can open theseports in the firewall. But what if opening ports in the firewall is not an option? The onlything left to do in this situation is tunneling the RMI communication over a port that'salready open.

The standard way to do this for RMI is to use HTTP tunneling, but this introduces asignificant security loophole because the cgi script used for this can redirect anyincoming request to any port (see Resources for more information). It also requiresinstallation of a cgi script on the webserver for which we may need the cooperation ofour webserver administrator. To avoid these problems we're going to use an SSHtunnel instead.

To create the SSH tunnel we need to use local port forwarding to forward traffic fromspecified local ports to the remote ports used for the RMI registry and the JMXConnector Service. We can do this using the following command:

ssh -L 11199:test-srv:11199 -L 12199:test-srv:12199 user@test-srv

This will forward all traffic sent to port 11199 and 12199 on the local machine to thesame ports on the remote machine named 'test-srv'. If you want to use putty instead,use the Tunnels configuration to add the local ports that need to be forwarded, asshown below.

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

10 de 14 2/11/11 3:50 AM

Page 11: Componative - Monitor Your Applications With JConsole - Part 3

After setting up the SSH tunnel we can now start JConsole and connect tolocalhost:11199 using Remote Process. This will create a connection to the RMIregistry running on port 11199 on the remote machine via the SSH tunnel. Howeverafter some time JConsole will report that it has failed to create a connection. Thereason for this is that after accessing the RMI registry via the tunnel JConsole will tryto connect to the JMX Connector Service directly through the firewall, as we can see inthe logging below (192.168.1.4 is the remote machine).

So although we created tunnels for both ports we can only instruct JConsole to use thelocal port for the RMI registry. After that we have no way to tell JConsole that is shouldalso use the local port (and the tunnel) for the JMX Connector Service, leaving thesecond tunnel unused. Luckily we have total control over our custom JMX ConnectorService so we can add some functionality to force JConsole to use the local port whenconnecting to the JMX Connector Service.

The way this works is as follows. When we create the JMX Connector Service we cancontrol the RMIClientSocketFactory and RMIServerSocketFactory that will beused, as we have done in the createJMXConnectorServerEnvironment() methodwhen SSL is enabled. By simply creating a RMIClientSocketFactory that will alwaysconnect to local ports we can force JConsole to use the tunnel instead of trying to gothrough the firewall.

public class RMISSHClientSocketFactory implements RMIClientSocketFactory, Serializable { private static final long serialVersionUID = -724208021737243414L; private RMIClientSocketFactory socketFactory = null;

public RMISSHClientSocketFactory(RMIClientSocketFactory socketFactory) { this.socketFactory = socketFactory; }

public Socket createSocket(String host, int port) throws IOException { host = "localhost"; if (this.socketFactory != null) { return this.socketFactory.createSocket(host, port); } else { return new Socket(host, port); } }

@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false;

final RMISSHClientSocketFactory other = (RMISSHClientSocketFactory) obj; if (this.socketFactory == null) { return other.socketFactory == null; } else { return this.socketFactory.equals(other.socketFactory); } }

@Override public int hashCode() { final int PRIME = 31;

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

11 de 14 2/11/11 3:50 AM

Page 12: Componative - Monitor Your Applications With JConsole - Part 3

int result = 1; result = PRIME * result + ((socketFactory == null) ? 0 : socketFactory.hashCode()); return result; } }

The RMIClientSocketFactory above can be used as a wrapper by passing anotherRMIClientSocketFactory during construction which comes in handy when SSL isenabled. Now al we need to do is add a new attribute to the ServiceMBean so we canenable tunneling and use the attribute in thecreateJMXConnectorServerEnvironment() method.

public class JConsoleService extends ServiceMBeanSupport implements JConsoleServiceMBean {

...

private boolean sshTunnelEnabled = false;

...

public JConsoleService( int rmiRegistryPort, int jmxConnectorServerPort, String passwordFile, String accessFile, boolean sslEnabled, boolean sslClientAuthenticationEnabled, boolean sslRegistryAuthenticationEnabled, boolean sshTunnelEnabled) {

super(); setRMIRegistryPort(rmiRegistryPort); setJMXConnectorServerPort(jmxConnectorServerPort); setPasswordFile(passwordFile); setAccessFile(accessFile); setSSLEnabled(sslEnabled); setSSLClientAuthenticationEnabled(sslClientAuthenticationEnabled); setSSLRegistryAuthenticationEnabled(sslRegistryAuthenticationEnabled); setSSHTunnelEnabled(sshTunnelEnabled); }

...

public void setSSHTunnelEnabled(boolean enabled) { this.sshTunnelEnabled = enabled; }

public boolean isSSHTunnelEnabled() { return this.sshTunnelEnabled; }

...

private Map<String,Object> createJMXConnectorServerEnvironment() { Map<String,Object> env = new HashMap<String,Object>();

// set password and access file if (!JConsoleService.NO_FILE.equals(getPasswordFile())) { env.put("jmx.remote.x.password.file", getPasswordFile()); } if (!JConsoleService.NO_FILE.equals(getAccessFile())) { env.put("jmx.remote.x.access.file", getAccessFile()); }

RMIClientSocketFactory csf = null; RMIServerSocketFactory ssf = null; if (isSSLEnabled()) { csf = new SslRMIClientSocketFactory(); ssf = new SslRMIServerSocketFactory( null, null, isSSLClientAuthenticationEnabled()); env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);

if (isSSLRegistryAuthenticationEnabled()) { env.put("com.sun.jndi.rmi.factory.socket", csf); } }

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

12 de 14 2/11/11 3:50 AM

Page 13: Componative - Monitor Your Applications With JConsole - Part 3

if (isSSHTunnelEnabled()) { RMIClientSocketFactory csfWrapper = new RMISSHClientSocketFactory(csf); env.put( RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csfWrapper); }

return env; }

...

}

Finally we need to add the new attribute to the jboss-service.xml so it will bepassed to the constructor.

<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="com.componative.jboss.services.jconsole.JConsoleService" name="com.componative.jboss:service=JConsoleService"> <constructor> <!-- RMIRegistry port --> <arg type="int" value="11199"/> <!-- JMXConnectorServer port --> <arg type="int" value="12199"/> <!-- password file --> <arg type="java.lang.String" value="-"/> <!-- access file --> <arg type="java.lang.String" value="-"/> <!-- enable SSL --> <arg type="boolean" value="false"/> <!-- enable SSL client authentication --> <arg type="boolean" value="false"/> <!-- enable SSL client authentication for registry --> <arg type="boolean" value="false"/> <!-- enable SSH tunnel --> <arg type="boolean" value="true"/> </constructor> </mbean> </server>

Because we created our own RMIClientSocketFactory we also need to add this classto the classpath of JConsole. To do this we create a separate jar containing just thisone class (see Resources for downloads) and set the classpath when we startJConsole. This gives us the following command for starting JConsole including loggingand keystore settings:

jconsole -J-Djava.util.logging.config.file=logging.properties -J-Djavax.net.ssl.trustStore=.jconsoleKeyStore -J-Djavax.net.ssl.keyStore=.jconsoleKeyStore -J-Djavax.net.ssl.keyStorePassword=secret -J-Djava.class.path=../lib/jconsole.jar;../lib/tools.jar;rmi-tunnel-factory.jar

This time JConsole will use the tunnel that we've created for port 12199 whenconnecting to the JMX Connector Service. If we take one final look at the logging we'llsee that the logging still shows the IP number of the remote machine since theredirection to localhost is done on a lower level. However the logging will show usthat the RMISSHClientSocketFactory is being used.

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

13 de 14 2/11/11 3:50 AM

Page 14: Componative - Monitor Your Applications With JConsole - Part 3

Site Map|Contact UsCopyright 2007 © Componative B.V. All rights reserved.

Author BioMike Schouten is a professionalsoftware developer with over10 years of experience. Hestarted out as a C++ developerand switched to Java in 2000.In 2001 he co-foundedComponative, where he worksas a developer and architect.

If JConsole still fails to create a connection via the created SSH tunnels, try adding the-v option when setting up the tunnels. This will log extra debug information when thetunnels are created that may shed some light on the problem. Running netstat -a todisplay the connections and listening ports may also be useful.

Having connected JConsole to JBoss via an SSH tunnel, you may try enabling otheroptions like password authentication or SSL client authentication. Just remember thatchanges made via the JBoss JMX console will be lost after a restart. If you found theconfiguration you need, modify the jboss-service.xml in the .sar file to make thechanges permanent.

This brings us to the end of our exploration of JConsole. We've seen how we can useJConsole to monitor JBoss on local and remote machines and how we can deal with afirewall. If you want to try out the JConsoleService that we've created in this last part,you can find the downloads in the Resources section.

Resources

Read the Mimicking Out-of-the-BoxManagement Using the JMX Remote APIsection of the Java SE Monitoring andManagement Guide for more informationabout using the JMX Remote API.For more information about implementing aJBoss Service visit theServiceMBeanSupport page of the JBossWiki.To learn more about developing MBeansread the Standard MBeans section of the Java Management Extensions tutorial.Read RMI Firewall Issues for more information about HTTP tunneling for RMI.To download the JConsoleService binaries and sources go to the Downloadsection.For feedback and questions you can send an e-mail to :[email protected]

Componative - Monitor Your Applications With JConsole - Part 3 http://www.componative.com/content/controller/developer/in...

14 de 14 2/11/11 3:50 AM