This is an automated email from the ASF dual-hosted git repository. samt pushed a commit to branch cassandra-3.11 in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit bbe09d496c2f460ccdeae1f54b24c94916e377bd Merge: fa403bb 4815ae7 Author: Sam Tunnicliffe <s...@beobal.com> AuthorDate: Wed Mar 25 17:30:35 2020 +0000 Merge branch 'cassandra-3.0' into cassandra-3.11 CHANGES.txt | 1 + .../org/apache/cassandra/utils/JMXServerUtils.java | 61 +++++++++++++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --cc CHANGES.txt index bc6ad0e,58798fa..a9c2540 --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,8 -1,7 +1,9 @@@ -3.0.21 +3.11.7 + * Allow sstableloader to use SSL on the native port (CASSANDRA-14904) +Merged from 3.0: * Run evictFromMembership in GossipStage (CASSANDRA-15592) Merged from 2.2: + * Disable JMX rebinding (CASSANDRA-15653) * Fix Commit log replays when static column clustering keys are collections (CASSANDRA-14365) * Fix Red Hat init script on newer systemd versions (CASSANDRA-15273) * Allow EXTRA_CLASSPATH to work on tar/source installations (CASSANDRA-15567) diff --cc src/java/org/apache/cassandra/utils/JMXServerUtils.java index 056bd6c,0000000..48d02f7 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/utils/JMXServerUtils.java +++ b/src/java/org/apache/cassandra/utils/JMXServerUtils.java @@@ -1,275 -1,0 +1,324 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 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.AccessException; ++import java.rmi.AlreadyBoundException; ++import java.rmi.NoSuchObjectException; ++import java.rmi.NotBoundException; ++import java.rmi.Remote; ++import java.rmi.RemoteException; +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); + + /** + * Creates a server programmatically. This allows us to set parameters which normally are + * inaccessable. + */ + @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()); + } + + // Configure the RMI client & server socket factories, including SSL config. + env.putAll(configureJmxSocketFactories(serverAddress, local)); + - // configure the RMI registry to use the socket factories we just created - Registry registry = LocateRegistry.createRegistry(port, - (RMIClientSocketFactory) env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE), - (RMIServerSocketFactory) env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE)); ++ // configure the RMI registry ++ Registry registry = new JmxRegistry(port, ++ (RMIClientSocketFactory) env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE), ++ (RMIServerSocketFactory) env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE), ++ "jmxrmi"); + + // Configure authn, using a JMXAuthenticator which either wraps a set log LoginModules configured + // via a JAAS configuration entry, or one which delegates to the standard file based authenticator. + // Authn is disabled if com.sun.management.jmxremote.authenticate=false + env.putAll(configureJmxAuthentication()); + + // Configure authz - if a custom proxy class is specified an instance will be returned. + // If not, but a location for the standard access file is set in system properties, the + // return value is null, and an entry is added to the env map detailing that location + // If neither method is specified, no access control is applied + MBeanServerForwarder authzProxy = configureJmxAuthorization(env); + + // Mark the JMX server as a permanently exported object. This allows the JVM to exit with the + // server running and also exempts it from the distributed GC scheduler which otherwise would + // potentially attempt a full GC every `sun.rmi.dgc.server.gcInterval` millis (default is 3600000ms) + // For more background see: + // - CASSANDRA-2967 + // - https://www.jclarity.com/2015/01/27/rmi-system-gc-unplugged/ + // - https://bugs.openjdk.java.net/browse/JDK-6760712 + env.put("jmx.remote.x.daemon", "true"); + + // Set the port used to create subsequent connections to exported objects over RMI. This simplifies + // configuration in firewalled environments, but it can't be used in conjuction with SSL sockets. + // See: CASSANDRA-7087 + int rmiPort = Integer.getInteger("com.sun.management.jmxremote.rmi.port", 0); + + // We create the underlying RMIJRMPServerImpl so that we can manually bind it to the registry, + // rather then specifying a binding address in the JMXServiceURL and letting it be done automatically + // when the server is started. The reason for this is that if the registry is configured with SSL + // sockets, the JMXConnectorServer acts as its client during the binding which means it needs to + // have a truststore configured which contains the registry's certificate. Manually binding removes + // this problem. + // See CASSANDRA-12109. + 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 a custom authz proxy was created, attach it to the server now. + if (authzProxy != null) + jmxServer.setMBeanServerForwarder(authzProxy); + jmxServer.start(); + - registry.rebind("jmxrmi", server); ++ ((JmxRegistry)registry).setRemoteServerStub(server.toStub()); + 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; + + // If authentication is enabled, initialize the appropriate JMXAuthenticator + // and stash it in the environment settings. + // A JAAS configuration entry takes precedence. If one is supplied, use + // Cassandra's own custom JMXAuthenticator implementation which delegates + // auth to the LoginModules specified by the JAAS configuration entry. + // If no JAAS entry is found, an instance of the JDK's own + // JMXPluggableAuthenticator is created. In that case, the admin may have + // set a location for the JMX password file which must be added to env + // before creating the authenticator. If no password file has been + // explicitly set, it's read from the default location + // $JAVA_HOME/lib/management/jmxremote.password + 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) + { + // stash the password file location where JMXPluggableAuthenticator expects it + 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) + { + // If a custom authz proxy is supplied (Cassandra ships with AuthorizationProxy, which + // delegates to its own role based IAuthorizer), then instantiate and return one which + // can be set as the JMXConnectorServer's MBeanServerForwarder. + // If no custom proxy is supplied, check system properties for the location of the + // standard access file & stash it in 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 + { + // hostnames based on IPv6 addresses must be wrapped in [ ] + 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); + } + } ++ ++ /* ++ * Better to use the internal API than re-invent the wheel. ++ */ ++ @SuppressWarnings("restriction") ++ private static class JmxRegistry extends sun.rmi.registry.RegistryImpl { ++ private final String lookupName; ++ private Remote remoteServerStub; ++ ++ JmxRegistry(final int port, ++ final RMIClientSocketFactory csf, ++ RMIServerSocketFactory ssf, ++ final String lookupName) throws RemoteException { ++ super(port, csf, ssf); ++ this.lookupName = lookupName; ++ } ++ ++ @Override ++ public Remote lookup(String s) throws RemoteException, NotBoundException { ++ return lookupName.equals(s) ? remoteServerStub : null; ++ } ++ ++ @Override ++ public void bind(String s, Remote remote) throws RemoteException, AlreadyBoundException, AccessException { ++ } ++ ++ @Override ++ public void unbind(String s) throws RemoteException, NotBoundException, AccessException { ++ } ++ ++ @Override ++ public void rebind(String s, Remote remote) throws RemoteException, AccessException { ++ } ++ ++ @Override ++ public String[] list() throws RemoteException { ++ return new String[] {lookupName}; ++ } ++ ++ public void setRemoteServerStub(Remote remoteServerStub) { ++ this.remoteServerStub = remoteServerStub; ++ } ++ } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org