Last active
August 29, 2015 14:22
-
-
Save frode-carlsen/23a322d5854df224b40b to your computer and use it in GitHub Desktop.
Configure JMX over SSH instead of SSL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2015 Frode Carlsen. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package fc.jmx; | |
import java.io.IOException; | |
import java.io.Serializable; | |
import java.lang.management.ManagementFactory; | |
import java.net.InetAddress; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
import java.rmi.registry.LocateRegistry; | |
import java.rmi.server.RMISocketFactory; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Set; | |
import javax.management.MBeanServer; | |
import javax.management.MalformedObjectNameException; | |
import javax.management.ObjectName; | |
import javax.management.remote.JMXConnectorServer; | |
import javax.management.remote.JMXConnectorServerFactory; | |
import javax.management.remote.JMXServiceURL; | |
import javax.management.remote.rmi.RMIConnectorServer; | |
import java.util.logging.Logger; | |
import java.util.logging.Level; | |
/** | |
* JMX configurator which allows remote connections to a Java VM over an SSH connection with an application specified port, instead of requiring SSL and having to set up certificates. | |
* To use - set up port-forwarding over SSH, and connect to that port from the client. This is achieved by only allowing connections over the loopback interface. | |
* Both the RMI registry and the JMX connection are set the same, so as to avoid dynamic ports (which makes it easier to use through firewalls, iptables rules and SELinux) | |
* <p> | |
* Initialize on startup of application. Should only be initiailzied once as it forces certain rmi/jmx relevant properties | |
* It creates a local socket proxy for jmx which can be connected to over the loopback interface. | |
* <p> | |
* Beware that password authentication and roles are not enabled in the default configuration below., but are trivial to add in for the industrious reader. | |
*/ | |
public class JmxOverSshConfigurator { | |
private static final Logger log = Logger.getLogger(JmxOverSshConfigurator.class.getName()); | |
private final Map<String, Object> env = new HashMap<>(); | |
private final int port; | |
private JMXConnectorServer cs; | |
private String domain; | |
public static synchronized JmxOverSshConfigurator initJmx(String domain, int port) { | |
return startJmxAgent(domain, port); | |
} | |
private JmxOverSshConfigurator(String domain, int port) throws IOException { | |
this.domain = domain; | |
this.port = port; | |
System.setProperty("java.rmi.server.randomIDs", "true"); | |
// Make sure our RMI server knows which host we're binding to | |
System.setProperty("java.rmi.server.hostname", InetAddress.getLoopbackAddress().getHostAddress()); | |
LoopbackSocketFactory ssf = new LoopbackSocketFactory(); | |
env.put("com.sun.management.jmxremote.authenticate", "false"); | |
env.put("com.sun.management.jmxremote.ssl", "false"); | |
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); | |
LocateRegistry.createRegistry(port, null, ssf); | |
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); | |
// Se http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html#gdfvq | |
// Monitoring applications through a firewall | |
String serviceUrl = "service:jmx:rmi://localhost:" + | |
(port) + // RMIService, RMIConnection port | |
"/jndi/rmi://localhost:" + | |
(port) // RMIRegistry | |
+ "/jmxrmi"; | |
log.log(Level.CONFIG, "JMX: Trying to connect to registry : " + serviceUrl); | |
JMXServiceURL url = new JMXServiceURL(serviceUrl); | |
cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); | |
} | |
private void start() throws IOException { | |
cs.start(); | |
log.log(Level.CONFIG, "JMX remote interface started at port [" + port + "]"); | |
} | |
public synchronized void stop() { | |
try { | |
cs.stop(); | |
} catch (IOException e) { | |
log.log(Level.INFO, "Caught IO exception while closing JMX interface", e); | |
} | |
log.log(Level.CONFIG, "JMX remote interface stopped for port [" + port + "]"); | |
stopJmx(); | |
} | |
private void stopJmx() { | |
removeAllMBeans(domain + ":*"); | |
} | |
private static JmxOverSshConfigurator startJmxAgent(String domain, int jmxPort) { | |
JmxOverSshConfigurator jmxConfigurator; | |
try { | |
jmxConfigurator = new JmxOverSshConfigurator(domain, jmxPort); | |
jmxConfigurator.start(); | |
return jmxConfigurator; | |
} catch (IOException e) { | |
// do not throw exception, but log error, as it may fail when running over vpn | |
log.log(Level.SEVERE, "Could not start JMX on port " + jmxPort, e); | |
return null; | |
} | |
} | |
public static void removeAllMBeans(String mbeanQuery) { | |
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); | |
Set<ObjectName> names = mbs.queryNames(createMBeanObjectNameFullyQualified(mbeanQuery), null); | |
for (ObjectName objName : names) { | |
try { | |
mbs.unregisterMBean(objName); | |
} catch (Exception ignored) { // NOSONAR | |
} | |
} | |
} | |
private static ObjectName createMBeanObjectNameFullyQualified(String navn) { | |
try { | |
return new ObjectName(navn); | |
} catch (MalformedObjectNameException e) { | |
throw new IllegalArgumentException("Invalid mbean name [" + navn + "]", e); | |
} | |
} | |
public static class LoopbackSocketFactory extends RMISocketFactory implements Serializable { | |
@Override | |
public ServerSocket createServerSocket(int port) throws IOException { | |
return new ServerSocket(port, 0, InetAddress.getLoopbackAddress()); | |
} | |
@Override | |
public Socket createSocket(String host, int port) throws IOException { | |
InetAddress loopback = InetAddress.getLoopbackAddress(); | |
if (host.equals(loopback.getHostAddress()) || host.equals(loopback.getHostName())) { | |
// just call the default client socket factory | |
return RMISocketFactory.getDefaultSocketFactory() | |
.createSocket(host, port); | |
} else { | |
throw new UnsupportedOperationException("Only RMI connections to loopback interface supported, cannot connect to host=" + host + ", port=" | |
+ port); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment