//retrieve the Principal Database assigned to JMX authentication duties
String jmxDatabaseName = appRegistry.getConfiguration().getJMXPrincipalDatabase();
Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases();
PrincipalDatabase db = map.get(jmxDatabaseName);
final JMXConnectorServer cs;
HashMap<String,Object> env = new HashMap<String,Object>();
//Socket factories for the RMIConnectorServer, either default or SLL depending on configuration
RMIClientSocketFactory csf;
RMIServerSocketFactory ssf;
//check ssl enabled option in config, default to true if option is not set
boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled();
if (sslEnabled)
{
//set the SSL related system properties used by the SSL RMI socket factories to the values
//given in the configuration file, unless command line settings have already been specified
String keyStorePath;
if(System.getProperty("javax.net.ssl.keyStore") != null)
{
keyStorePath = System.getProperty("javax.net.ssl.keyStore");
}
else
{
keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath();
}
//check the keystore path value is valid
if (keyStorePath == null)
{
throw new ConfigurationException("JMX management SSL keystore path not defined, " +
"unable to start SSL protected JMX ConnectorServer");
}
else
{
//ensure the system property is set
System.setProperty("javax.net.ssl.keyStore", keyStorePath);
//check the file is usable
File ksf = new File(keyStorePath);
if (!ksf.exists())
{
throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf + "\n"
+ "Check broker configuration, or see create-example-ssl-stores script"
+ "in the bin/ directory if you need to generate an example store.");
}
if (!ksf.canRead())
{
throw new FileNotFoundException("Cannot read JMX management SSL keystore file: "
+ ksf + ". Check permissions.");
}
_log.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
_startupLog.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
}
//check the key store password is set
if (System.getProperty("javax.net.ssl.keyStorePassword") == null)
{
if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null)
{
throw new ConfigurationException("JMX management SSL keystore password not defined, " +
"unable to start requested SSL protected JMX server");
}
else
{
System.setProperty("javax.net.ssl.keyStorePassword",
appRegistry.getConfiguration().getManagementKeyStorePassword());
}
}
//create the SSL RMI socket factories
csf = new SslRMIClientSocketFactory();
ssf = new SslRMIServerSocketFactory();
_log.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
(port +PORT_EXPORT_OFFSET) + ") with SSL");
_startupLog.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
(port +PORT_EXPORT_OFFSET) + ") with SSL");
}
else
{
//Do not specify any specific RMI socket factories, resulting in use of the defaults.
csf = null;
ssf = null;
_log.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
_startupLog.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
}
//add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server
RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator();
rmipa.setPrincipalDatabase(db);
env.put(JMXConnectorServer.AUTHENTICATOR, rmipa);
/*
* Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub.
* Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI.
* As a result, only binds made using the object reference will succeed, thus securing it from external change.
*/
System.setProperty("java.rmi.server.randomIDs", "true");
_rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory());
/*
* We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls
* to bind the ConnectorServer to the registry, which will now fail as for security we have
* locked it from any RMI based modifications, including our own. Instead, we will manually bind
* the RMIConnectorServer stub to the registry using its object reference, which will still succeed.
*
* The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer
* on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's.
*/
final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env);
final String hostname = InetAddress.getLocalHost().getHostName();
final JMXServiceURL externalUrl = new JMXServiceURL(
"service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi");
final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET);
cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer)
{
@Override
public synchronized void start() throws IOException
{
try
{
//manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent
_rmiRegistry.bind("jmxrmi", rmiConnectorServerStub);
}
catch (AlreadyBoundException abe)
{
//key was already in use. shouldnt happen here as its a new registry, unbindable by normal means.
//IOExceptions are the only checked type throwable by the method, wrap and rethrow
IOException ioe = new IOException(abe.getMessage());
ioe.initCause(abe);
throw ioe;
}
//now do the normal tasks
super.start();
}
@Override
public JMXServiceURL getAddress()
{
//must return our pre-crafted url that includes the full details, inc JNDI details
return externalUrl;
}
};
//Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer.
MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance();
cs.setMBeanServerForwarder(mbsf);
cs.start();
}