package org.apache.cassandra.utils;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.management.remote.*;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.management.remote.rmi.RMIJRMPServerImpl;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import javax.security.auth.Subject;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jmx.remote.security.JMXPluggableAuthenticator;
import org.apache.cassandra.auth.jmx.AuthenticationProxy;
public class JMXServerUtils
{
private static final Logger logger = LoggerFactory.getLogger(JMXServerUtils.class);
@SuppressWarnings("resource")
public static JMXConnectorServer createJMXServer(int port, boolean local)
throws IOException
{
Map<String, Object> env = new HashMap<>();
InetAddress serverAddress = null;
if (local)
{
serverAddress = InetAddress.getLoopbackAddress();
System.setProperty("java.rmi.server.hostname", serverAddress.getHostAddress());
}
env.putAll(configureJmxSocketFactories(serverAddress, local));
Registry registry = LocateRegistry.createRegistry(port,
(RMIClientSocketFactory) env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE),
(RMIServerSocketFactory) env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
env.putAll(configureJmxAuthentication());
MBeanServerForwarder authzProxy = configureJmxAuthorization(env);
env.put("jmx.remote.x.daemon", "true");
int rmiPort = Integer.getInteger("com.sun.management.jmxremote.rmi.port", 0);
RMIJRMPServerImpl server = new RMIJRMPServerImpl(rmiPort,
(RMIClientSocketFactory) env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE),
(RMIServerSocketFactory) env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE),
env);
JMXServiceURL serviceURL = new JMXServiceURL("rmi", null, rmiPort);
RMIConnectorServer jmxServer = new RMIConnectorServer(serviceURL, env, server, ManagementFactory.getPlatformMBeanServer());
if (authzProxy != null)
jmxServer.setMBeanServerForwarder(authzProxy);
jmxServer.start();
registry.rebind("jmxrmi", server);
logJmxServiceUrl(serverAddress, port);
return jmxServer;
}
private static Map<String, Object> configureJmxAuthentication()
{
Map<String, Object> env = new HashMap<>();
if (!Boolean.getBoolean("com.sun.management.jmxremote.authenticate"))
return env;
String configEntry = System.getProperty("cassandra.jmx.remote.login.config");
if (configEntry != null)
{
env.put(JMXConnectorServer.AUTHENTICATOR, new AuthenticationProxy(configEntry));
}
else
{
String passwordFile = System.getProperty("com.sun.management.jmxremote.password.file");
if (passwordFile != null)
{
env.put("jmx.remote.x.password.file", passwordFile);
}
env.put(JMXConnectorServer.AUTHENTICATOR, new JMXPluggableAuthenticatorWrapper(env));
}
return env;
}
private static MBeanServerForwarder configureJmxAuthorization(Map<String, Object> env)
{
String authzProxyClass = System.getProperty("cassandra.jmx.authorizer");
if (authzProxyClass != null)
{
final InvocationHandler handler = FBUtilities.construct(authzProxyClass, "JMX authz proxy");
final Class[] interfaces = { MBeanServerForwarder.class };
Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler);
return MBeanServerForwarder.class.cast(proxy);
}
else
{
String accessFile = System.getProperty("com.sun.management.jmxremote.access.file");
if (accessFile != null)
{
env.put("jmx.remote.x.access.file", accessFile);
}
return null;
}
}
private static Map<String, Object> configureJmxSocketFactories(InetAddress serverAddress, boolean localOnly)
{
Map<String, Object> env = new HashMap<>();
if (Boolean.getBoolean("com.sun.management.jmxremote.ssl"))
{
boolean requireClientAuth = Boolean.getBoolean("com.sun.management.jmxremote.ssl.need.client.auth");
String[] protocols = null;
String protocolList = System.getProperty("com.sun.management.jmxremote.ssl.enabled.protocols");
if (protocolList != null)
{
System.setProperty("javax.rmi.ssl.client.enabledProtocols", protocolList);
protocols = StringUtils.split(protocolList, ',');
}
String[] ciphers = null;
String cipherList = System.getProperty("com.sun.management.jmxremote.ssl.enabled.cipher.suites");
if (cipherList != null)
{
System.setProperty("javax.rmi.ssl.client.enabledCipherSuites", cipherList);
ciphers = StringUtils.split(cipherList, ',');
}
SslRMIClientSocketFactory clientFactory = new SslRMIClientSocketFactory();
SslRMIServerSocketFactory serverFactory = new SslRMIServerSocketFactory(ciphers, protocols, requireClientAuth);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverFactory);
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientFactory);
env.put("com.sun.jndi.rmi.factory.socket", clientFactory);
logJmxSslConfig(serverFactory);
}
else if (localOnly)
{
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
new RMIServerSocketFactoryImpl(serverAddress));
}
return env;
}
private static void logJmxServiceUrl(InetAddress serverAddress, int port)
{
String urlTemplate = "service:jmx:rmi://%1$s/jndi/rmi://%1$s:%2$d/jmxrmi";
String hostName;
if (serverAddress == null)
{
hostName = FBUtilities.getBroadcastAddress() instanceof Inet6Address ? "[::]" : "0.0.0.0";
}
else
{
hostName = serverAddress instanceof Inet6Address
? '[' + serverAddress.getHostAddress() + ']'
: serverAddress.getHostAddress();
}
String url = String.format(urlTemplate, hostName, port);
logger.info("Configured JMX server at: {}", url);
}
private static void logJmxSslConfig(SslRMIServerSocketFactory serverFactory)
{
logger.debug("JMX SSL configuration. { protocols: [{}], cipher_suites: [{}], require_client_auth: {} }",
serverFactory.getEnabledProtocols() == null
? "'JVM defaults'"
: Arrays.stream(serverFactory.getEnabledProtocols()).collect(Collectors.joining("','", "'", "'")),
serverFactory.getEnabledCipherSuites() == null
? "'JVM defaults'"
: Arrays.stream(serverFactory.getEnabledCipherSuites()).collect(Collectors.joining("','", "'", "'")),
serverFactory.getNeedClientAuth());
}
private static class JMXPluggableAuthenticatorWrapper implements JMXAuthenticator
{
final Map<?, ?> env;
private JMXPluggableAuthenticatorWrapper(Map<?, ?> env)
{
this.env = ImmutableMap.copyOf(env);
}
public Subject authenticate(Object credentials)
{
JMXPluggableAuthenticator authenticator = new JMXPluggableAuthenticator(env);
return authenticator.authenticate(credentials);
}
}
}