views:

1032

answers:

5

While there appears to some documentation on how to expose JMX through various firewall and tunneling schemes, I sort of want the opposite. I want to ensure that JMX is only accessible to local machine. Unfortunately it appears that the "out of the box" management options do not allow restricting the ports to a local interface and netstat shows them listening on any/all interfaces.

http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html#gdevf

I have to admit I am bewildered by the layers of indirection in JMX, the RMI registry, connectors, adapters, etc.

I would like to just turn it on and then tunnel through SSH as opposed to exposing it to the world and then having to perform arduous and superfluous user management and security configuration. It would be nice to be able to use a built in RMI registry and not have to run an external one.

+1  A: 

Can't help with the sun way of doing it. Even after jmx adapters started coming with the jdk (6 i think?) I kept using mx4j for the least-effort adapter setup. It is trivial to start up an mx4j http adapter on 127.0.0.1 or an internal-only interface. Then SOP was to ssh in with port forwards or use scripts with wget commands.

http://mx4j.sourceforge.net/

navicore
A: 

Unfortunately there is currently no way to do that.

According to Sun documentation a sole -Dcom.sun.management.jmxremote should open only a local port while -Dcom.sun.management.jmxremote.port= opens a remotely accessible port.

Both ways open an additional random port which is accessible from remote.

I've seen -Dcom.sun.management.jmxremote.host=, but that does not seem to have any effect.

I came to the conclusion that there is no way and used a local firewall to shield the server.

+2  A: 

A bit late answer but if it is still a problem for you (or someone else), I think this will do the trick:

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMISocketFactory;

import javax.management.MBeanServer;
import javax.management.remote.*;


public class LocalJMXPort {

    public static void main(String[] args) {
     try {
      int port = 12468;
      // Create an instance of our own socket factory (see below)
      RMISocketFactory factory = new LocalHostSocketFactory();

      // Set it as default
      RMISocketFactory.setSocketFactory(factory);

      // Create our registry
      LocateRegistry.createRegistry(port);

       // Get the MBeanServer and setup a JMXConnectorServer
      MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
      JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://127.0.0.1:"+port+"/jndi/rmi://127.0.0.1:"+port+"/jmxrmi");
      JMXConnectorServer rmiServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
      rmiServer.start();

      // Say something
      System.out.println("Connect your jconsole to localhost:"+port+". Press a key to exit");

      // Wait for a key press
      int in = System.in.read();
      //Exit
      System.out.println("Exiting");
      System.exit(0);
     } catch(Exception ex) {
      ex.printStackTrace();
     }
    }

    static private class LocalHostSocketFactory extends RMISocketFactory {
     public ServerSocket createServerSocket(int port) throws IOException {
      ServerSocket ret = new ServerSocket();
      ret.bind(new InetSocketAddress("localhost", port));
      return ret;
     }

     public Socket createSocket(String host, int port) throws IOException {
      return new Socket(host, port);
     }
    }
}

I just put it together and it is possible that I did something really stupid because my only objective was to have it binding to localhost:port instead of *:port and that part seems to work.

Feel free to comment if there are things that could be bettered or is just plain stupid.

Fredrik
A: 

Frederic answers worked fine for me with addition that I had to use the same socket factory on the client.

      RMISocketFactory.setSocketFactory(new LocalHostSocketFactory());
+1  A: 

If you're accessing from the local host then it is possible to do what JConsole and JVisualVM do in this case, which is to use the Attach API to find the local-only address of the server (what you get if you run with -Dcom.sun.management.jmxremote but not -Dcom.sun.management.jmxremote.port=N) and connect to that. In another answer, Thraidh says that a remotely-accessible port is opened even in this case, which was true in earlier versions but has not been so for a couple of years.

Fredrik's solution works but is overkill. You only need to define an RMIServerSocketFactory, not an RMISocketFactory (which defines both client and server). This eliminates the need to configure the client specially. The code at http://vafer.org/blog/20061010091658 looks correct to me.

The "out-of-the-box" management constructed with command-line properties like -Dcom.sun.management.jmxremote can only take you so far before you need to start programming with the JMX API itself. We've generally been reluctant to have the out-of-the-box management evolve into a complete parallel API, which is why there are problems like this one that are out of its reach. We explain how to go from one to the other here.

Éamonn McManus, JMX Spec Lead

Eamonn McManus