Make JMX SSL configurable in cassandra.yaml

patch by Maulin Vasavada; reviewed by Stefan Miklosovic, Doug Rohrer for CASSANDRA-18508
This commit is contained in:
Maulin Vasavada 2024-07-23 23:27:29 -07:00 committed by Stefan Miklosovic
parent 8b126e97bb
commit 9131be9fa5
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
47 changed files with 2410 additions and 252 deletions

View File

@ -1,4 +1,5 @@
5.1
* Make JMX SSL configurable in cassandra.yaml (CASSANDRA-18508)
* Fix cqlsh CAPTURE command to save query results without trace details when TRACING is ON (CASSANDRA-19105)
* Optionally prevent tombstone purging during repair (CASSANDRA-20071)
* Add post-filtering support for the IN operator in SAI queries (CASSANDRA-20025)

View File

@ -88,6 +88,7 @@ New features
generate a password of configured password strength policy upon role creation or alteration
when 'GENERATED PASSWORD' clause is used. Character sets supported are: English, Cyrillic, modern Cyrillic,
German, Polish and Czech.
- JMX SSL configuration can be now done in cassandra.yaml via jmx_encryption_options section instead of cassandra-env.sh
Upgrading

View File

@ -236,6 +236,11 @@ else
JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
# jmx ssl options
# Consider using the jmx_encryption_options section of cassandra.yaml instead
# to prevent sensitive information being exposed.
# In case jmx ssl options are configured in both the places - this file and cassandra.yaml, and
# if com.sun.management.jmxremote.ssl is configured to be true here and encryption_options are marked enabled in
# cassandra.yaml, then we will get exception at the startup
#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl=true"
#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl.need.client.auth=true"
#JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl.enabled.protocols=<enabled-protocols>"

View File

@ -1712,6 +1712,24 @@ client_encryption_options:
# warnings will be reported during the session establishment.
# certificate_validity_warn_threshold: 10d
# Configure SSL for JMX
#
# Using system properties via cassandra-env.sh to configure JMX SSL is
# considered legacy and only supported for backward compatibility. If SSL
# is enabled via both methods, a configuration error will occur at
# startup. Hot reloading of the `SSLContext` is not yet supported for the
# JMX SSL.
# Similar to `client/server_encryption_options`, you can specify PEM-based
# key material or customize the SSL configuration using `ssl_context_factory` in `jmx_encryption_options`.
#jmx_encryption_options:
# enabled: true
# cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
# accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
# keystore: conf/cassandra_ssl.keystore
# keystore_password: cassandra
# truststore: conf/cassandra_ssl.truststore
# truststore_password: cassandra
# internode_compression controls whether traffic between nodes is
# compressed.
# Can be:

View File

@ -1676,6 +1676,24 @@ client_encryption_options:
# TLS_RSA_WITH_AES_256_CBC_SHA
# ]
# Configure SSL for JMX
#
# Using system properties via cassandra-env.sh to configure JMX SSL is
# considered legacy and only supported for backward compatibility. If SSL
# is enabled via both methods, a configuration error will occur at
# startup. Hot reloading of the `SSLContext` is not yet supported for the
# JMX SSL.
# Similar to `client/server_encryption_options`, you can specify PEM-based
# key material or customize the SSL configuration using `ssl_context_factory` in `jmx_encryption_options`.
#jmx_encryption_options:
# enabled: true
# cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
# accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
# keystore: conf/cassandra_ssl.keystore
# keystore_password: cassandra
# truststore: conf/cassandra_ssl.truststore
# truststore_password: cassandra
# internode_compression controls whether traffic between nodes is
# compressed.
# Can be:

View File

@ -78,7 +78,8 @@ files having the required PEM data as shown below,
* Configuration: PEM keys/certs defined in-line (mind the spaces in the
YAML!)
....
[source,yaml]
----
client/server_encryption_options:
ssl_context_factory:
class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
@ -97,17 +98,19 @@ YAML!)
-----BEGIN CERTIFICATE-----
<your base64 encoded certificate>
-----END CERTIFICATE-----
....
----
* Configuration: PEM keys/certs defined in files
....
[source,yaml]
----
client/server_encryption_options:
ssl_context_factory:
class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
keystore: <file path to the keystore file in the PEM format with the private key and the certificate chain>
keystore_password: "<your password if the private key is encrypted with a password>"
truststore: <file path to the truststore file in the PEM format>
....
----
== SSL Certificate Hot Reloading
@ -564,8 +567,61 @@ See also: xref:cassandra:developing/cql/security.adoc#permissions[`Permissions`]
=== JMX With SSL
JMX SSL configuration is controlled by a number of system properties,
some of which are optional. To turn on SSL, edit the relevant lines in
JMX SSL configuration should be specified in `cassandra.yaml` under
`encryption_options`, similar to how `client/server_encryption_options`
are configured. Using system properties to configure JMX SSL is
considered legacy and only supported for backward compatibility. If SSL
is enabled via both methods, a configuration error will occur at
startup. Hot reloading of the `SSLContext` is not yet supported for the
JMX SSL.
==== JMX SSL configuration in cassandra.yaml
Below is an example of configuring JMX SSL in `cassandra.yaml`
[source,yaml]
----
jmx_encryption_options:
enabled: true
cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
keystore: test/conf/cassandra_ssl_test.keystore
keystore_password: cassandra
truststore: test/conf/cassandra_ssl_test.truststore
truststore_password: cassandra
----
Similar to `client/server_encryption_options`, you can specify PEM-based
key material or customize the SSL configuration using
`ssl_context_factory` in `jmx_encryption_options`.
Below is an example of configuring PEM based key material,
[source,yaml]
----
jmx_encryption_options:
enabled: true
cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
ssl_context_factory:
class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
parameters:
private_key: |
-----BEGIN ENCRYPTED PRIVATE KEY----- OR -----BEGIN PRIVATE KEY-----
<your base64 encoded private key>
-----END ENCRYPTED PRIVATE KEY----- OR -----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
<your base64 encoded certificate chain>
-----END CERTIFICATE-----
private_key_password: "<your password if the private key is encrypted with a password>"
trusted_certificates: |
-----BEGIN CERTIFICATE-----
<your base64 encoded certificate>
-----END CERTIFICATE-----
----
==== (Legacy) JMX SSL configuration with system properties
To turn on SSL this way, edit the relevant lines in
`cassandra-env.sh` to uncomment and
set the values of these properties as required:

View File

@ -49,6 +49,7 @@ COMPLEX_OPTIONS = (
'hints_compression',
'server_encryption_options',
'client_encryption_options',
'jmx_encryption_options',
'transparent_data_encryption_options',
'hinted_handoff_disabled_datacenters',
'startup_checks',

View File

@ -114,7 +114,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest
* In order to test with real 'env' variables comment out this line and set appropriate env variable. This is
* done to avoid having a dependency on env in the unit test.
*/
commonConfig.put("require_client_auth", "false";
commonConfig.put("require_client_auth", "false");
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
}

View File

@ -434,6 +434,7 @@ public class Config
public EncryptionOptions.ServerEncryptionOptions server_encryption_options = new EncryptionOptions.ServerEncryptionOptions();
public EncryptionOptions client_encryption_options = new EncryptionOptions();
public EncryptionOptions jmx_encryption_options = new EncryptionOptions();
public InternodeCompression internode_compression = InternodeCompression.none;
@ -1320,6 +1321,7 @@ public class Config
private static final Set<String> SENSITIVE_KEYS = new HashSet<String>() {{
add("client_encryption_options");
add("server_encryption_options");
add("jmx_encryption_options");
}};
public static void log(Config config)

View File

@ -960,6 +960,9 @@ public class DatabaseDescriptor
if (conf.client_encryption_options != null)
conf.client_encryption_options.applyConfig();
if (conf.jmx_encryption_options != null)
conf.jmx_encryption_options.applyConfig();
if (conf.snapshot_links_per_second < 0)
throw new ConfigurationException("snapshot_links_per_second must be >= 0");
@ -1306,7 +1309,14 @@ public class DatabaseDescriptor
{
SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, REQUIRED, true);
SSLFactory.validateSslContext("Native transport", conf.client_encryption_options, conf.client_encryption_options.getClientAuth(), true);
// For JMX SSL the validation is pretty much the same as the Native transport
SSLFactory.validateSslContext("JMX transport", conf.jmx_encryption_options, conf.jmx_encryption_options.getClientAuth(), true);
SSLFactory.initHotReloading(conf.server_encryption_options, conf.client_encryption_options, false);
/*
For JMX SSL, the hot reloading of the SSLContext is out of scope for CASSANDRA-18508.
Since JMXServerUtil that initializes the JMX Server is used statically, it may require significant
effort to change that behavior unlike SSLFactory used for Native transport/Internode messaging.
*/
}
catch (IOException e)
{
@ -3653,6 +3663,11 @@ public class DatabaseDescriptor
return conf.client_encryption_options;
}
public static EncryptionOptions getJmxEncryptionOptions()
{
return conf.jmx_encryption_options;
}
@VisibleForTesting
public static void updateNativeProtocolEncryptionOptions(Function<EncryptionOptions, EncryptionOptions> update)
{

View File

@ -172,10 +172,10 @@ public class EncryptionOptions
ConfigKey(String keyName)
{
this.keyName=keyName;
this.keyName = keyName;
}
String getKeyName()
public String toString()
{
return keyName;
}
@ -184,8 +184,9 @@ public class EncryptionOptions
{
Set<String> valueSet = new HashSet<>();
ConfigKey[] values = values();
for(ConfigKey key: values) {
valueSet.add(toLowerCaseLocalized(key.getKeyName()));
for (ConfigKey key : values)
{
valueSet.add(toLowerCaseLocalized(key.toString()));
}
return valueSet;
}
@ -357,7 +358,7 @@ public class EncryptionOptions
protected static void putSslContextFactoryParameter(Map<String, Object> existingParameters, ConfigKey configKey, Object value)
{
if (value != null) {
existingParameters.put(configKey.getKeyName(), value);
existingParameters.put(configKey.toString(), value);
}
}

View File

@ -37,24 +37,25 @@ import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
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.JMXAuthenticator;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.MBeanServerForwarder;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.management.remote.rmi.RMIJRMPServerImpl;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import javax.net.ssl.SSLException;
import javax.security.auth.Subject;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.jmx.AuthenticationProxy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.utils.jmx.DefaultJmxSocketFactory;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_JMX_AUTHORIZER;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_JMX_REMOTE_LOGIN_CONFIG;
@ -62,12 +63,6 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MA
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_AUTHENTICATE;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_PASSWORD_FILE;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_RMI_PORT;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_RMI_SERVER_HOSTNAME;
public class JMXServerUtils
@ -92,6 +87,7 @@ public class JMXServerUtils
}
// Configure the RMI client & server socket factories, including SSL config.
// CASSANDRA-18508: Make JMX SSL to be configured in cassandra.yaml
env.putAll(configureJmxSocketFactories(serverAddress, local));
// configure the RMI registry
@ -226,46 +222,33 @@ public class JMXServerUtils
}
}
private static Map<String, Object> configureJmxSocketFactories(InetAddress serverAddress, boolean localOnly)
/**
* Configures the client and server socket factories for the JMX connection. It uses {@link DefaultJmxSocketFactory}
* for configuring this.
*
* @param serverAddress the JMX server is bound to
* @param localOnly {@code true} if the JMX server only allows local connections; {@code false} if the JMX server
* allows the remote connections.
* @return Map&lt;String, Object@gt; containing {@code jmx.remote.rmi.client.socket.factory}, {@code jmx.remote.rmi.server.socket.factory}
* and {@code com.sun.jndi.rmi.factory.socket} properties for the client and server socket factories.
* @throws SSLException if it fails to configure the socket factories with the given input
*
* @see DefaultJmxSocketFactory
*/
@VisibleForTesting
public static Map<String, Object> configureJmxSocketFactories(InetAddress serverAddress, boolean localOnly) throws SSLException
{
Map<String, Object> env = new HashMap<>();
if (COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean())
{
boolean requireClientAuth = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH.getBoolean();
String[] protocols = null;
String protocolList = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS.getString();
if (protocolList != null)
{
JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.setString(protocolList);
protocols = StringUtils.split(protocolList, ',');
}
String[] ciphers = null;
String cipherList = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES.getString();
if (cipherList != null)
{
JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.setString(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;
return new DefaultJmxSocketFactory().configure(serverAddress, localOnly, DatabaseDescriptor.getJmxEncryptionOptions());
}
@VisibleForTesting
public static void logJmxServiceUrl(InetAddress serverAddress, int port)
{
logger.info("Configured JMX server at: {}", getJmxServiceUrl(serverAddress, port));
}
@VisibleForTesting
private static String getJmxServiceUrl(InetAddress serverAddress, int port)
{
String urlTemplate = "service:jmx:rmi://%1$s/jndi/rmi://%1$s:%2$d/jmxrmi";
String hostName;
@ -280,21 +263,7 @@ public class JMXServerUtils
? '[' + serverAddress.getHostAddress() + ']'
: serverAddress.getHostAddress();
}
String url = String.format(urlTemplate, hostName, port);
logger.info("Configured JMX server at: {}", url);
}
private static void logJmxSslConfig(SslRMIServerSocketFactory serverFactory)
{
if (logger.isDebugEnabled())
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());
return String.format(urlTemplate, hostName, port);
}
private static class JMXPluggableAuthenticatorWrapper implements JMXAuthenticator

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.rmi.server.RMIClientSocketFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -32,8 +31,9 @@ import java.util.Objects;
* which can otherwise be influenced by the system property "java.rmi.server.hostname" in strange and
* unpredictable ways.
*/
public class RMIClientSocketFactoryImpl implements RMIClientSocketFactory, Serializable
public class RMIClientSocketFactoryImpl implements Serializable, RMICloseableClientSocketFactory
{
private static final long serialVersionUID = 955153017775496366L;
List<Socket> sockets = new ArrayList<>();
private final InetAddress localAddress;
@ -50,6 +50,7 @@ public class RMIClientSocketFactoryImpl implements RMIClientSocketFactory, Seria
return socket;
}
@Override
public void close() throws IOException
{
for (Socket socket: sockets)

View File

@ -0,0 +1,29 @@
/*
* 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.rmi.server.RMIClientSocketFactory;
/**
* This represents closeable RMI Client Socket factory. It extends {@link AutoCloseable} and can be used with
* {@code try-with-resources}.
*/
public interface RMICloseableClientSocketFactory extends RMIClientSocketFactory, AutoCloseable
{
}

View File

@ -0,0 +1,29 @@
/*
* 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.rmi.server.RMIServerSocketFactory;
/**
* This represents closeable RMI Server Socket factory. It extends {@link AutoCloseable} and can be used with
* {@code try-with-resources}.
*/
public interface RMICloseableServerSocketFactory extends RMIServerSocketFactory, AutoCloseable
{
}

View File

@ -0,0 +1,208 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.exceptions.ConfigurationException;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
/**
* Abstracts out the most common workflow in setting up the SSL client and server socket factorires for JMX.
* First, it checks the system properties (see <a href="https://docs.oracle.com/en/java/javase/17/management/monitoring-and-management-using-jmx-technology.html#GUID-F08985BB-629A-4FBF-A0CB-8762DF7590E0">Java Documentation</a> to read the SSL configuration.
* Next, it checks the provided {@code jmxEncryptionOptions} to read the SSL configuration.
* If none of them is enabled, it checks the provided {@code localOnly} flag to configure the JMX server socket
* factory for the local JMX connection.
*/
abstract public class AbstractJmxSocketFactory
{
private static final Logger logger = LoggerFactory.getLogger(AbstractJmxSocketFactory.class);
/**
* Configures the client and server socket factories for the JMX connection.
* Specifically it configures below properties as applicable,
* <pre>
* jmx.remote.rmi.client.socket.factory
* jmx.remote.rmi.server.socket.factory
* com.sun.jndi.rmi.factory.socket
* </pre>
* <p>
* In case of remote connection enabled, this also sets the following system properties,
* <pre>
* com.sun.management.jmxremote.ssl=true
* javax.rmi.ssl.client.enabledCipherSuites=&lt;applicable cipher suites provided in the configuration&gt;
* javax.rmi.ssl.client.enabledProtocols=&lt;applicable protocols provided in the configuration&gt;
* </pre>
*
* @param serverAddress the JMX server is bound to
* @param localOnly {@code true} if the JMX server only allows local connections; {@code false} if the JMX server
* allows the remote connections.
* @param jmxEncryptionOptions {@link EncryptionOptions} used for the SSL configuration in case of the remote
* connections. Could be {@code null} if system properties are
* used instead as per <a href="https://docs.oracle.com/en/java/javase/17/management/monitoring-and-management-using-jmx-technology.html#GUID-F08985BB-629A-4FBF-A0CB-8762DF7590E0">Java Documentation</a>
* @return Map&lt;String, Object@gt; containing {@code jmx.remote.rmi.client.socket.factory}, {@code jmx.remote.rmi.server.socket.factory}
* and {@code com.sun.jndi.rmi.factory.socket} properties for the client and server socket factories.
* @throws SSLException if it fails to configure the socket factories with the given input
*/
public Map<String, Object> configure(InetAddress serverAddress, boolean localOnly,
EncryptionOptions jmxEncryptionOptions) throws SSLException
{
Map<String, Object> env = new HashMap<>();
boolean jmxRemoteSslSystemConfigProvided = COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean();
// We check for the enabled jmx_encryption_options here because in case of no configuration provided in cassandra.yaml
// it will default to empty/non-null encryption options. Hence, we consider it set only if 'enabled' flag is set to true
boolean jmxEncryptionOptionsProvided = jmxEncryptionOptions != null
&& jmxEncryptionOptions.getEnabled() != null
&& jmxEncryptionOptions.getEnabled();
if (jmxRemoteSslSystemConfigProvided && jmxEncryptionOptionsProvided)
{
throw new ConfigurationException("Please specify JMX SSL configuration in either cassandra-env.sh or " +
"cassandra.yaml, not in both locations");
}
boolean requireClientAuth = false;
String[] ciphers = null;
String[] protocols = null;
SSLContext sslContext = null;
if (jmxRemoteSslSystemConfigProvided)
{
logger.info("Enabling JMX SSL using environment file properties");
logger.warn("Consider using the jmx_encryption_options section of cassandra.yaml instead to prevent " +
"sensitive information being exposed");
requireClientAuth = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH.getBoolean();
String protocolList = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS.getString();
if (protocolList != null)
{
JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.setString(protocolList);
protocols = StringUtils.split(protocolList, ',');
}
String cipherList = COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES.getString();
if (cipherList != null)
{
JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.setString(cipherList);
ciphers = StringUtils.split(cipherList, ',');
}
configureSslClientSocketFactory(env, serverAddress);
configureSslServerSocketFactory(env, serverAddress, ciphers, protocols, requireClientAuth);
}
else if (jmxEncryptionOptionsProvided)
{
logger.info("Enabling JMX SSL using jmx_encryption_options from cassandra.yaml");
// Here we can continue to use the SslRMIClientSocketFactory for client sockets.
// However, we should still set System properties for cipher_suites and enabled_protocols
// to have the same behavior as cassandra-env.sh based JMX SSL settings
setJmxSystemProperties(jmxEncryptionOptions);
requireClientAuth = jmxEncryptionOptions.getClientAuth() == EncryptionOptions.ClientAuth.REQUIRED;
ciphers = jmxEncryptionOptions.cipherSuitesArray();
protocols = jmxEncryptionOptions.acceptedProtocolsArray();
sslContext = jmxEncryptionOptions.sslContextFactoryInstance
.createJSSESslContext(jmxEncryptionOptions.getClientAuth());
configureSslClientSocketFactory(env, serverAddress);
configureSslServerSocketFactory(env, serverAddress, ciphers, protocols, requireClientAuth, sslContext);
}
else if (localOnly)
{
configureLocalSocketFactories(env, serverAddress);
}
return env;
}
/**
* Configures the non-SSL socket factories for the local JMX.
*
* @param env output param containing the configured socket factories
* @param serverAddress the JMX server is bound to
*/
abstract public void configureLocalSocketFactories(Map<String, Object> env, InetAddress serverAddress);
/**
* Configures SSL based client socket factory.
*
* @param env output param containing the configured socket factories
* @param serverAddress the JMX server is bound to
*/
abstract public void configureSslClientSocketFactory(Map<String, Object> env, InetAddress serverAddress);
/**
* Configures SSL based server socket factory based on system config for key/trust stores.
*
* @param env output param containing the configured socket factories
* @param serverAddress the JMX server is bound to
* @param enabledCipherSuites for the SSL communication
* @param enabledProtocols for the SSL communication
* @param needClientAuth {@code true} if it requires the client-auth; {@code false} otherwise
*/
abstract public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress,
String[] enabledCipherSuites, String[] enabledProtocols,
boolean needClientAuth);
/**
* Configures SSL based server socket factory based on custom SSLContext.
*
* @param env output param containing the configured socket factories
* @param serverAddress the JMX server is bound to
* @param enabledCipherSuites for the SSL communication
* @param enabledProtocols for the SSL communication
* @param needClientAuth {@code true} if it requires the client-auth; {@code false} otherwise
* @param sslContext for the SSL communication
*/
abstract public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress,
String[] enabledCipherSuites, String[] enabledProtocols,
boolean needClientAuth, SSLContext sslContext);
/**
* Sets the following JMX system properties.
* <pre>
* com.sun.management.jmxremote.ssl=true
* javax.rmi.ssl.client.enabledCipherSuites=&lt;applicable cipher suites provided in the configuration&gt;
* javax.rmi.ssl.client.enabledProtocols=&lt;applicable protocols provided in the configuration&gt;
* </pre>
*
* @param jmxEncryptionOptions for the SSL communication
*/
private void setJmxSystemProperties(EncryptionOptions jmxEncryptionOptions)
{
COM_SUN_MANAGEMENT_JMXREMOTE_SSL.setBoolean(true);
if (jmxEncryptionOptions.getAcceptedProtocols() != null)
JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.setString(StringUtils.join(jmxEncryptionOptions.getAcceptedProtocols(), ","));
if (jmxEncryptionOptions.cipherSuitesArray() != null)
JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.setString(StringUtils.join(jmxEncryptionOptions.cipherSuitesArray(), ","));
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLContext;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.utils.RMIServerSocketFactoryImpl;
/**
* Default implementation of the JMX Socket factory.
*/
public final class DefaultJmxSocketFactory extends AbstractJmxSocketFactory
{
private static final Logger logger = LoggerFactory.getLogger(DefaultJmxSocketFactory.class);
@Override
public void configureLocalSocketFactories(Map<String, Object> env, InetAddress serverAddress)
{
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new RMIServerSocketFactoryImpl(serverAddress));
}
@Override
public void configureSslClientSocketFactory(Map<String, Object> env, InetAddress serverAddress)
{
SslRMIClientSocketFactory clientFactory = new SslRMIClientSocketFactory();
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientFactory);
env.put("com.sun.jndi.rmi.factory.socket", clientFactory);
}
@Override
public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth)
{
SslRMIServerSocketFactory serverFactory = new SslRMIServerSocketFactory(enabledCipherSuites, enabledProtocols, needClientAuth);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverFactory);
logJmxSslConfig(serverFactory);
}
@Override
public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth, SSLContext sslContext)
{
SslRMIServerSocketFactory serverFactory = new SslRMIServerSocketFactory(sslContext, enabledCipherSuites, enabledProtocols, needClientAuth);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverFactory);
logJmxSslConfig(serverFactory);
}
private void logJmxSslConfig(SslRMIServerSocketFactory serverFactory)
{
if (logger.isDebugEnabled())
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());
}
}

View File

@ -0,0 +1,147 @@
#
# 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.
#
#
# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
# implementation classes.
#
cluster_name: Test Cluster
# memtable_allocation_type: heap_buffers
memtable_allocation_type: offheap_objects
commitlog_sync: batch
commitlog_segment_size: 5MiB
commitlog_directory: build/test/cassandra/commitlog
# commitlog_compression:
# - class_name: LZ4Compressor
cdc_raw_directory: build/test/cassandra/cdc_raw
cdc_enabled: false
hints_directory: build/test/cassandra/hints
partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
listen_address: 127.0.0.1
storage_port: 7012
ssl_storage_port: 17012
start_native_transport: true
native_transport_port: 9042
column_index_size: 4KiB
saved_caches_directory: build/test/cassandra/saved_caches
data_file_directories:
- build/test/cassandra/data
disk_access_mode: mmap_index_only
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "127.0.0.1:7012"
endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
dynamic_snitch: true
jmx_encryption_options:
enabled: false
cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
ssl_context_factory:
class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
parameters:
private_key: |
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
FujZhqbKUDbYAcqTkoQ=
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
n3MVF9w=
-----END CERTIFICATE-----
private_key_password: "cassandra"
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
n3MVF9w=
-----END CERTIFICATE-----
incremental_backups: true
concurrent_compactors: 4
compaction_throughput: 0MiB/s
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
row_cache_size: 16MiB
user_defined_functions_enabled: true
scripted_user_defined_functions_enabled: false
prepared_statements_cache_size: 1MiB
corrupted_tombstone_strategy: exception
stream_entire_sstables: true
stream_throughput_outbound: 24MiB/s
sasi_indexes_enabled: true
materialized_views_enabled: true
file_cache_enabled: true

View File

@ -0,0 +1,147 @@
#
# 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.
#
#
# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
# implementation classes.
#
cluster_name: Test Cluster
# memtable_allocation_type: heap_buffers
memtable_allocation_type: offheap_objects
commitlog_sync: batch
commitlog_segment_size: 5MiB
commitlog_directory: build/test/cassandra/commitlog
# commitlog_compression:
# - class_name: LZ4Compressor
cdc_raw_directory: build/test/cassandra/cdc_raw
cdc_enabled: false
hints_directory: build/test/cassandra/hints
partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
listen_address: 127.0.0.1
storage_port: 7012
ssl_storage_port: 17012
start_native_transport: true
native_transport_port: 9042
column_index_size: 4KiB
saved_caches_directory: build/test/cassandra/saved_caches
data_file_directories:
- build/test/cassandra/data
disk_access_mode: mmap_index_only
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "127.0.0.1:7012"
endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
dynamic_snitch: true
jmx_encryption_options:
enabled: true
cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
ssl_context_factory:
class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
parameters:
private_key: |
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
FujZhqbKUDbYAcqTkoQ=
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
n3MVF9w=
-----END CERTIFICATE-----
private_key_password: "cassandra"
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
n3MVF9w=
-----END CERTIFICATE-----
incremental_backups: true
concurrent_compactors: 4
compaction_throughput: 0MiB/s
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
row_cache_size: 16MiB
user_defined_functions_enabled: true
scripted_user_defined_functions_enabled: false
prepared_statements_cache_size: 1MiB
corrupted_tombstone_strategy: exception
stream_entire_sstables: true
stream_throughput_outbound: 24MiB/s
sasi_indexes_enabled: true
materialized_views_enabled: true
file_cache_enabled: true

View File

@ -0,0 +1,72 @@
#
# 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.
#
#
# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
# implementation classes.
#
cluster_name: Test Cluster
# memtable_allocation_type: heap_buffers
memtable_allocation_type: offheap_objects
commitlog_sync: batch
commitlog_segment_size: 5MiB
commitlog_directory: build/test/cassandra/commitlog
# commitlog_compression:
# - class_name: LZ4Compressor
cdc_raw_directory: build/test/cassandra/cdc_raw
cdc_enabled: false
hints_directory: build/test/cassandra/hints
partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
listen_address: 127.0.0.1
storage_port: 7012
ssl_storage_port: 17012
start_native_transport: true
native_transport_port: 9042
column_index_size: 4KiB
saved_caches_directory: build/test/cassandra/saved_caches
data_file_directories:
- build/test/cassandra/data
disk_access_mode: mmap_index_only
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "127.0.0.1:7012"
endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
dynamic_snitch: true
jmx_encryption_options:
enabled: true
cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1]
keystore: test/conf/cassandra_ssl_test.keystore
keystore_password: cassandra
truststore: test/conf/cassandra_ssl_test.truststore
truststore_password: cassandra
incremental_backups: true
concurrent_compactors: 4
compaction_throughput: 0MiB/s
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
row_cache_size: 16MiB
user_defined_functions_enabled: true
scripted_user_defined_functions_enabled: false
prepared_statements_cache_size: 1MiB
corrupted_tombstone_strategy: exception
stream_entire_sstables: true
stream_throughput_outbound: 24MiB/s
sasi_indexes_enabled: true
materialized_views_enabled: true
file_cache_enabled: true

View File

@ -22,20 +22,21 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.SocketException;
import java.rmi.server.RMIServerSocketFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.net.ServerSocketFactory;
import org.apache.cassandra.utils.RMICloseableServerSocketFactory;
/**
* This class is used to keep track of RMI servers created during a cluster creation so we can
* later close the sockets, which would otherwise be left with a thread running waiting for
* connections that would never show up as the server was otherwise closed.
*/
class CollectingRMIServerSocketFactoryImpl implements RMIServerSocketFactory
class CollectingRMIServerSocketFactoryImpl implements RMICloseableServerSocketFactory
{
private final InetAddress bindAddress;
List<ServerSocket> sockets = new ArrayList<>();
@ -62,7 +63,7 @@ class CollectingRMIServerSocketFactoryImpl implements RMIServerSocketFactory
return result;
}
@Override
public void close() throws IOException
{
for (ServerSocket socket : sockets)

View File

@ -0,0 +1,160 @@
/*
* 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.distributed.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.cassandra.utils.RMICloseableServerSocketFactory;
/**
* This class is used to keep track of SSL based RMI servers created during a cluster creation to
* later close the sockets, which would otherwise be left with a thread running waiting for
* connections that would never show up as the server was otherwise closed.
*/
class CollectingSslRMIServerSocketFactoryImpl implements RMICloseableServerSocketFactory
{
private final InetAddress bindAddress;
private final String[] enabledCipherSuites;
private final String[] enabledProtocols;
private final boolean needClientAuth;
private final SSLSocketFactory sslSocketFactory;
List<ServerSocket> sockets = new ArrayList<>();
public CollectingSslRMIServerSocketFactoryImpl(InetAddress bindAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth, SSLContext sslContext)
{
this.bindAddress = bindAddress;
this.enabledCipherSuites = enabledCipherSuites;
this.enabledProtocols = enabledProtocols;
this.needClientAuth = needClientAuth;
this.sslSocketFactory = sslContext.getSocketFactory();
}
public CollectingSslRMIServerSocketFactoryImpl(InetAddress bindAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth)
{
this.bindAddress = bindAddress;
this.enabledCipherSuites = enabledCipherSuites;
this.enabledProtocols = enabledProtocols;
this.needClientAuth = needClientAuth;
this.sslSocketFactory = getDefaultSSLSocketFactory();
}
public String[] getEnabledCipherSuites()
{
return enabledCipherSuites;
}
public String[] getEnabledProtocols()
{
return enabledProtocols;
}
public boolean isNeedClientAuth()
{
return needClientAuth;
}
@Override
public ServerSocket createServerSocket(int pPort) throws IOException
{
ServerSocket result = createSslServerSocket(pPort);
try
{
result.setReuseAddress(true);
}
catch (SocketException e)
{
result.close();
throw e;
}
sockets.add(result);
return result;
}
private ServerSocket createSslServerSocket(int pPort) throws IOException
{
return new ServerSocket(pPort, 0, bindAddress)
{
public Socket accept() throws IOException
{
Socket socket = super.accept();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
socket, socket.getInetAddress().getHostName(),
socket.getPort(), true);
sslSocket.setUseClientMode(false);
if (enabledCipherSuites != null)
{
sslSocket.setEnabledCipherSuites(enabledCipherSuites);
}
if (enabledProtocols != null)
{
sslSocket.setEnabledProtocols(enabledProtocols);
}
sslSocket.setNeedClientAuth(needClientAuth);
return sslSocket;
}
};
}
@Override
public void close() throws IOException
{
for (ServerSocket socket : sockets)
{
socket.close();
}
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CollectingSslRMIServerSocketFactoryImpl that = (CollectingSslRMIServerSocketFactoryImpl) o;
return Objects.equals(bindAddress, that.bindAddress);
}
@Override
public int hashCode()
{
return Objects.hash(bindAddress);
}
private static SSLSocketFactory defaultSSLSocketFactory = null;
private static synchronized SSLSocketFactory getDefaultSSLSocketFactory()
{
if (defaultSSLSocketFactory == null)
defaultSSLSocketFactory =
(SSLSocketFactory) SSLSocketFactory.getDefault();
return defaultSSLSocketFactory;
}
}

View File

@ -708,9 +708,9 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
assert config.networkTopology().contains(config.broadcastAddress()) : String.format("Network topology %s doesn't contain the address %s",
config.networkTopology(), config.broadcastAddress());
DistributedTestSnitch.assign(config.networkTopology());
DatabaseDescriptor.daemonInitialization();
if (config.has(JMX))
startJmx();
DatabaseDescriptor.daemonInitialization();
LoggingSupportFactory.getLoggingSupport().onStartup();
logSystemInfo(inInstancelogger);

View File

@ -22,9 +22,9 @@ import java.io.IOException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
@ -34,12 +34,14 @@ import javax.management.remote.rmi.RMIJRMPServerImpl;
import com.google.common.util.concurrent.Uninterruptibles;
import org.slf4j.Logger;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.distributed.api.IInstance;
import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.shared.JMXUtil;
import org.apache.cassandra.utils.JMXServerUtils;
import org.apache.cassandra.utils.MBeanWrapper;
import org.apache.cassandra.utils.RMIClientSocketFactoryImpl;
import org.apache.cassandra.utils.RMICloseableClientSocketFactory;
import org.apache.cassandra.utils.RMICloseableServerSocketFactory;
import sun.rmi.transport.tcp.TCPEndpoint;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_RMI_DGC_LEASE_VALUE_IN_JVM_DTEST;
@ -57,17 +59,20 @@ public class IsolatedJmx
private JMXServerUtils.JmxRegistry registry;
private RMIJRMPServerImpl jmxRmiServer;
private MBeanWrapper.InstanceMBeanWrapper wrapper;
private RMIClientSocketFactoryImpl clientSocketFactory;
private CollectingRMIServerSocketFactoryImpl serverSocketFactory;
private RMICloseableClientSocketFactory clientSocketFactory;
private RMICloseableServerSocketFactory serverSocketFactory;
private Logger inInstancelogger;
private IInstanceConfig config;
public IsolatedJmx(IInstance instance, Logger inInstanceLogger) {
public IsolatedJmx(IInstance instance, Logger inInstanceLogger)
{
this.config = instance.config();
this.inInstancelogger = inInstanceLogger;
}
public void startJmx() {
@SuppressWarnings("unchecked")
public void startJmx()
{
try
{
// Several RMI threads hold references to in-jvm dtest objects, and are, by default, kept
@ -84,14 +89,17 @@ public class IsolatedJmx
String hostname = addr.getHostAddress();
wrapper = new MBeanWrapper.InstanceMBeanWrapper(hostname + ":" + jmxPort);
((MBeanWrapper.DelegatingMbeanWrapper) MBeanWrapper.instance).setDelegate(wrapper);
Map<String, Object> env = new HashMap<>();
serverSocketFactory = new CollectingRMIServerSocketFactoryImpl(addr);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
serverSocketFactory);
clientSocketFactory = new RMIClientSocketFactoryImpl(addr);
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
clientSocketFactory);
// CASSANDRA-18508: Sensitive JMX SSL configuration options can be easily exposed
Map<String, Object> encryptionOptionsMap = (Map<String, Object>) config.getParams().get("jmx_encryption_options");
EncryptionOptions jmxEncryptionOptions = getJmxEncryptionOptions(encryptionOptionsMap);
// Here the `localOnly` is always passed as true as it is for the local isolated JMX testing
// However if the `jmxEncryptionOptions` are provided or JMX SSL configuration is set it will configure
// the socket factories appropriately.
Map<String, Object> socketFactories = new IsolatedJmxSocketFactory().configure(addr, true, jmxEncryptionOptions);
serverSocketFactory = (RMICloseableServerSocketFactory) socketFactories.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
clientSocketFactory = (RMICloseableClientSocketFactory) socketFactories.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
Map<String, Object> env = new HashMap<>(socketFactories);
// configure the RMI registry
registry = new JMXServerUtils.JmxRegistry(jmxPort,
@ -137,6 +145,49 @@ public class IsolatedJmx
}
}
/**
* Builds {@code EncryptionOptions} from the map based SSL configuration properties.
*
* @param encryptionOptionsMap of SSL configuration properties
* @return EncryptionOptions built object
*/
@SuppressWarnings("unchecked")
private EncryptionOptions getJmxEncryptionOptions(Map<String, Object> encryptionOptionsMap)
{
if (encryptionOptionsMap == null)
{
return null;
}
EncryptionOptions jmxEncryptionOptions = new EncryptionOptions();
String[] cipherSuitesArray = (String[]) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.CIPHER_SUITES.toString());
if (cipherSuitesArray != null)
{
jmxEncryptionOptions = jmxEncryptionOptions.withCipherSuites(cipherSuitesArray);
}
List<String> acceptedProtocols = (List<String>) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.ACCEPTED_PROTOCOLS.toString());
if (acceptedProtocols != null)
{
jmxEncryptionOptions = jmxEncryptionOptions.withAcceptedProtocols(acceptedProtocols);
}
Boolean requireClientAuthValue = (Boolean) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.REQUIRE_CLIENT_AUTH.toString());
EncryptionOptions.ClientAuth requireClientAuth = requireClientAuthValue == null ?
EncryptionOptions.ClientAuth.NOT_REQUIRED :
EncryptionOptions.ClientAuth.from(String.valueOf(requireClientAuthValue));
Object enabledOption = encryptionOptionsMap.get(EncryptionOptions.ConfigKey.ENABLED.toString());
boolean enabled = enabledOption != null ? (Boolean) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.ENABLED.toString()) : false;
//CASSANDRA-18508 NOTE - We do not populate sslContextFactory configuration here for tests, it could be enhanced
jmxEncryptionOptions = jmxEncryptionOptions
.withKeyStore((String) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.KEYSTORE.toString()))
.withKeyStorePassword((String) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.KEYSTORE_PASSWORD.toString()))
.withTrustStore((String) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.TRUSTSTORE.toString()))
.withTrustStorePassword((String) encryptionOptionsMap.get(EncryptionOptions.ConfigKey.TRUSTSTORE_PASSWORD.toString()))
.withRequireClientAuth(requireClientAuth)
.withEnabled(enabled);
return jmxEncryptionOptions;
}
private void waitForJmxAvailability(Map<String, ?> env)
{
try (JMXConnector ignored = JMXUtil.getJmxConnector(config, 20, env))

View File

@ -0,0 +1,96 @@
/*
* 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.distributed.impl;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.utils.RMIClientSocketFactoryImpl;
import org.apache.cassandra.utils.jmx.AbstractJmxSocketFactory;
/**
* JMX Socket factory used for the isolated JMX testing.
*/
public class IsolatedJmxSocketFactory extends AbstractJmxSocketFactory
{
private static final Logger logger = LoggerFactory.getLogger(IsolatedJmxSocketFactory.class);
@Override
public void configureLocalSocketFactories(Map<String, Object> env, InetAddress serverAddress)
{
CollectingRMIServerSocketFactoryImpl serverSocketFactory = new CollectingRMIServerSocketFactoryImpl(serverAddress);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
serverSocketFactory);
RMIClientSocketFactoryImpl clientSocketFactory = new RMIClientSocketFactoryImpl(serverAddress);
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
clientSocketFactory);
}
@Override
public void configureSslClientSocketFactory(Map<String, Object> env, InetAddress serverAddress)
{
RMISslClientSocketFactoryImpl clientFactory = new RMISslClientSocketFactoryImpl(serverAddress,
CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString(),
CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientFactory);
env.put("com.sun.jndi.rmi.factory.socket", clientFactory);
}
@Override
public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth)
{
CollectingSslRMIServerSocketFactoryImpl serverFactory = new CollectingSslRMIServerSocketFactoryImpl(serverAddress,
enabledCipherSuites,
enabledProtocols,
needClientAuth);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverFactory);
logJmxSslConfig(serverFactory);
}
@Override
public void configureSslServerSocketFactory(Map<String, Object> env, InetAddress serverAddress, String[] enabledCipherSuites,
String[] enabledProtocols, boolean needClientAuth, SSLContext sslContext)
{
CollectingSslRMIServerSocketFactoryImpl serverFactory = new CollectingSslRMIServerSocketFactoryImpl(serverAddress,
enabledCipherSuites,
enabledProtocols,
needClientAuth,
sslContext);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverFactory);
logJmxSslConfig(serverFactory);
}
private void logJmxSslConfig(CollectingSslRMIServerSocketFactoryImpl serverFactory)
{
if (logger.isDebugEnabled())
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.isNeedClientAuth());
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.distributed.impl;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyStore;
import java.util.Map;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.io.util.File;
/**
* Simplified and independent version of {@link org.apache.cassandra.security.FileBasedSslContextFactory} for
* testing SSL based JMX clients that require configuring keystore and/or truststore.
*/
public class JmxTestClientSslContextFactory
{
private final Map<String, Object> parameters;
// keystore is not needed when the JMX server does not require client-auth
private final String keystore;
// keystore could be null in case JMX server does not require client-auth
private final String keystore_password;
private final String truststore;
private final String truststore_password;
private final String protocol;
private final String algorithm;
private final String store_type;
public JmxTestClientSslContextFactory(Map<String, Object> parameters)
{
this.parameters = parameters;
keystore = getString(EncryptionOptions.ConfigKey.KEYSTORE.toString());
keystore_password = getString(EncryptionOptions.ConfigKey.KEYSTORE_PASSWORD.toString());
truststore = getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString());
truststore_password = getString(EncryptionOptions.ConfigKey.TRUSTSTORE_PASSWORD.toString());
protocol = getString(EncryptionOptions.ConfigKey.PROTOCOL.toString(), "TLS");
algorithm = getString(EncryptionOptions.ConfigKey.ALGORITHM.toString());
store_type = getString(EncryptionOptions.ConfigKey.STORE_TYPE.toString(), "JKS");
}
private String getString(String key, String defaultValue)
{
return parameters.get(key) == null ? defaultValue : (String) parameters.get(key);
}
private String getString(String key)
{
return (String) parameters.get(key);
}
private TrustManagerFactory buildTrustManagerFactory() throws SSLException
{
try (InputStream tsf = Files.newInputStream(File.getPath(truststore)))
{
final String algorithm = this.algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : this.algorithm;
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
KeyStore ts = KeyStore.getInstance(store_type);
final char[] truststorePassword = StringUtils.isEmpty(truststore_password) ? null : truststore_password.toCharArray();
ts.load(tsf, truststorePassword);
tmf.init(ts);
return tmf;
}
catch (Exception e)
{
throw new SSLException("failed to build trust manager store for secure connections", e);
}
}
private KeyManagerFactory buildKeyManagerFactory() throws SSLException
{
final String algorithm = this.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : this.algorithm;
if (keystore != null)
{
try (InputStream ksf = Files.newInputStream(File.getPath(keystore)))
{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
KeyStore ks = KeyStore.getInstance(store_type);
final char[] password = keystore_password.toCharArray();
ks.load(ksf, password);
kmf.init(ks, password);
return kmf;
}
catch (Exception e)
{
throw new SSLException("failed to build key manager store for secure connections", e);
}
}
else
{
return null;
}
}
public SSLContext createSSLContext() throws SSLException
{
TrustManager[] trustManagers = buildTrustManagerFactory().getTrustManagers();
KeyManagerFactory kmf = buildKeyManagerFactory();
try
{
SSLContext ctx = SSLContext.getInstance(protocol);
ctx.init(kmf != null ? kmf.getKeyManagers() : null, trustManagers, null);
return ctx;
}
catch (Exception e)
{
throw new SSLException("Error creating/initializing the SSL Context", e);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.distributed.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.rmi.ssl.SslRMIClientSocketFactory;
/**
* {@code RMIClientSocketFactory} for testing SSL based JMX clients.
*/
public class JmxTestClientSslSocketFactory extends SslRMIClientSocketFactory implements Serializable
{
private static final long serialVersionUID = 818579127759449333L;
private final SocketFactory defaultSocketFactory;
private final String[] cipherSuites;
private final String[] acceptedProtocols;
public JmxTestClientSslSocketFactory(SSLContext sslContext, String[] cipherSuites, String[] acceptedProtocols)
{
this.cipherSuites = cipherSuites;
this.acceptedProtocols = acceptedProtocols;
defaultSocketFactory = sslContext == null ? SSLSocketFactory.getDefault() : sslContext.getSocketFactory();
}
@Override
public Socket createSocket(String host, int port) throws IOException
{
final SSLSocket sslSocket = (SSLSocket) defaultSocketFactory.createSocket(host, port);
if (cipherSuites != null)
sslSocket.setEnabledCipherSuites(cipherSuites);
if (acceptedProtocols != null)
sslSocket.setEnabledProtocols(acceptedProtocols);
return sslSocket;
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.distributed.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.cassandra.utils.RMICloseableClientSocketFactory;
/**
* {@code RMIClientSocketFactory} for testing SSL based JMX clients.
* This class is used to override the local address the JMX client calculates when trying to connect,
* which can otherwise be influenced by the system property "java.rmi.server.hostname" in strange and
* unpredictable ways.
*/
public class RMISslClientSocketFactoryImpl implements Serializable, RMICloseableClientSocketFactory
{
private static final long serialVersionUID = 9054380061905145241L;
private static final Pattern COMMA_SPLITTER = Pattern.compile(",");
private static final List<Socket> sockets = new ArrayList<>();
private final InetAddress localAddress;
private final String[] enabledCipherSuites;
private final String[] enabledProtocols;
public RMISslClientSocketFactoryImpl(InetAddress localAddress, String enabledCipherSuites, String enabledProtocls)
{
this.localAddress = localAddress;
this.enabledCipherSuites = splitCommaSeparatedString(enabledCipherSuites);
this.enabledProtocols = splitCommaSeparatedString(enabledProtocls);
}
@Override
public Socket createSocket(String host, int port) throws IOException
{
Socket socket = createSslSocket(port);
sockets.add(socket);
return socket;
}
private Socket createSslSocket(int port) throws IOException
{
final SocketFactory sslSocketFactory = SSLSocketFactory.getDefault();
final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(localAddress, port);
if (enabledCipherSuites != null)
{
try
{
sslSocket.setEnabledCipherSuites(enabledCipherSuites);
}
catch (IllegalArgumentException e)
{
throw new IOException(e.getMessage(), e);
}
}
if (enabledProtocols != null)
{
try
{
sslSocket.setEnabledProtocols(enabledProtocols);
}
catch (IllegalArgumentException e)
{
throw new IOException(e.getMessage(), e);
}
}
return sslSocket;
}
@Override
public void close() throws IOException
{
for (Socket socket : sockets)
{
try
{
socket.close();
}
catch (IOException ignored)
{
// intentionally ignored
}
}
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RMISslClientSocketFactoryImpl that = (RMISslClientSocketFactoryImpl) o;
return Objects.equals(localAddress, that.localAddress);
}
@Override
public int hashCode()
{
return Objects.hash(localAddress);
}
private String[] splitCommaSeparatedString(String stringToSplit)
{
if (stringToSplit == null)
return null;
return COMMA_SPLITTER.split(stringToSplit);
}
}

View File

@ -72,6 +72,13 @@ public final class WithProperties implements AutoCloseable
return set(prop, () -> prop.setLong(value));
}
public WithProperties preserve(CassandraRelevantProperties prop)
{
String previous = prop.getString(); // because all properties are strings
rollback.add(previous == null ? prop::clearValue : () -> prop.setString(previous));
return this;
}
private void with(String key, String value)
{
String previous = System.setProperty(key, value); // checkstyle: suppress nearby 'blockSystemPropertyUsage'

View File

@ -70,19 +70,19 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
final static String validTrustStorePassword = TlsTestUtils.SERVER_TRUSTSTORE_PASSWORD;
// Base configuration map for a valid keystore that can be opened
final static Map<String,Object> validKeystore = ImmutableMap.of("keystore", validKeyStorePath,
protected final static Map<String,Object> validFileBasedKeystores = ImmutableMap.of("keystore", validKeyStorePath,
"keystore_password", validKeyStorePassword,
"truststore", validTrustStorePath,
"truststore_password", validTrustStorePassword);
// Configuration with a valid keystore, but an unknown protocol
final static Map<String,Object> nonExistantProtocol = ImmutableMap.<String,Object>builder()
.putAll(validKeystore)
.putAll(validFileBasedKeystores)
.put("accepted_protocols", Collections.singletonList("NoProtocolIKnow"))
.build();
// Configuration with a valid keystore, but an unknown cipher suite
final static Map<String,Object> nonExistantCipher = ImmutableMap.<String,Object>builder()
.putAll(validKeystore)
.putAll(validFileBasedKeystores)
.put("cipher_suites", Collections.singletonList("NoCipherIKnow"))
.build();
@ -340,7 +340,7 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
}
/* Provde the cluster cannot start with the configured options */
void assertCannotStartDueToConfigurationException(Cluster cluster)
protected void assertCannotStartDueToConfigurationException(Cluster cluster)
{
Throwable tr = null;
try

View File

@ -53,7 +53,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
c.with(Feature.NETWORK);
c.set("ssl_storage_port", 7013);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "none")
.put("optional", false)
.put("legacy_ssl_storage_port_enabled", "true")
@ -87,7 +87,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options", validKeystore);
c.set("server_encryption_options", validFileBasedKeystores);
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
@ -111,7 +111,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
c.set("storage_port", 7012);
c.set("ssl_storage_port", 7013);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "none")
.put("optional", true)
.put("legacy_ssl_storage_port_enabled", "true")
@ -146,7 +146,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
c.set("storage_port", 7012); // must match in-jvm dtest assigned ports
c.set("ssl_storage_port", 7012);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "none")
.put("optional", true)
.put("legacy_ssl_storage_port_enabled", "true")
@ -174,7 +174,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "none")
.put("optional", false)
.build());
@ -202,7 +202,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
.with(Feature.GOSSIP) // To make sure AllMembersAliveMonitor checks gossip (which uses internode conns)
.with(Feature.NATIVE_PROTOCOL); // For virtual tables
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "all")
.build());
}).start())
@ -234,7 +234,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "all")
.put("accepted_protocols", ImmutableList.of("TLSv1.1", "TLSv1.2", "TLSv1.3"))
.build());
@ -272,7 +272,7 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NETWORK);
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "all")
.put("accepted_protocols", Collections.singletonList("TLSv1.2"))
.put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))

View File

@ -87,7 +87,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options", validKeystore);
c.set("client_encryption_options", validFileBasedKeystores);
}).createWithoutStarting())
{
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
@ -119,7 +119,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("accepted_protocols", ImmutableList.of("TLSv1.1", "TLSv1.2", "TLSv1.3"))
.build());
@ -156,7 +156,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("accepted_protocols", Collections.singletonList("TLSv1.2"))
.put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
@ -237,7 +237,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("require_client_auth", "optional")
.put("optional", true)
@ -282,7 +282,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("require_client_auth", "optional")
.put("optional", false)
@ -329,7 +329,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
// Server configuration for optional mTLS mode
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("require_client_auth", "false")
.put("optional", true)
@ -384,7 +384,7 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL);
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("require_client_auth", true)
.put("require_endpoint_verification", requireEndpointVerification)

View File

@ -49,7 +49,7 @@ import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.JMX;
import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
import static org.apache.cassandra.distributed.test.jmx.JMXGetterCheckTest.testAllValidGetters;
import static org.apache.cassandra.distributed.test.jmx.JMXTestsUtil.testAllValidGetters;
import static org.apache.cassandra.utils.FBUtilities.now;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.startsWith;
@ -167,7 +167,7 @@ public class ResourceLeakTest extends TestBaseImpl
Assert.assertThat(defaultDomain, startsWith(JMXUtil.getJmxHost(config) + ":" + config.jmxPort()));
}
}
testAllValidGetters(cluster);
testAllValidGetters(cluster, null);
}
catch (Exception e)
{

View File

@ -61,12 +61,12 @@ public class SSTableLoaderEncryptionOptionsTest extends AbstractEncryptionOption
CLUSTER = Cluster.build().withNodes(1).withConfig(c -> {
c.with(Feature.NATIVE_PROTOCOL, Feature.NETWORK, Feature.GOSSIP); // need gossip to get hostid for java driver
c.set("server_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("internode_encryption", "all")
.put("optional", false)
.build());
c.set("client_encryption_options",
ImmutableMap.builder().putAll(validKeystore)
ImmutableMap.builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("optional", false)
.put("accepted_protocols", Collections.singletonList("TLSv1.2"))

View File

@ -0,0 +1,44 @@
/*
* 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.distributed.test;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.distributed.shared.WithProperties;
public class WithPropertiesTest
{
@Test
public void testPreserveBeforeSet()
{
CassandraRelevantProperties booleanProperty = CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
boolean defaultPropertyValue = Boolean.parseBoolean(booleanProperty.getDefaultValue());
boolean newPropertyValue = true;
try (WithProperties properties = new WithProperties()
.preserve(booleanProperty)
.set(booleanProperty, newPropertyValue))
{
Assert.assertEquals("Property value must match", newPropertyValue, booleanProperty.getBoolean());
}
Assert.assertEquals("Property value must revert to the default value", defaultPropertyValue, booleanProperty.getBoolean());
}
}

View File

@ -36,7 +36,7 @@ import org.apache.cassandra.distributed.shared.ClusterUtils;
import org.apache.cassandra.distributed.shared.JMXUtil;
import org.apache.cassandra.distributed.test.TestBaseImpl;
import static org.apache.cassandra.distributed.test.jmx.JMXGetterCheckTest.testAllValidGetters;
import static org.apache.cassandra.distributed.test.jmx.JMXTestsUtil.testAllValidGetters;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
@ -85,7 +85,7 @@ public class JMXFeatureTest extends TestBaseImpl
allInstances.addAll(instancesContacted);
// Make sure we actually exercise the mbeans by testing a bunch of getters.
// Without this it's possible for the test to pass as we don't touch any mBeans that we register.
testAllValidGetters(cluster);
testAllValidGetters(cluster, null);
}
}
Assert.assertEquals("Each instance from each cluster should have been unique", iterations * 2, allInstances.size());
@ -122,7 +122,7 @@ public class JMXFeatureTest extends TestBaseImpl
Assert.assertThat(statusResult.getStderr(), is(blankOrNullString()));
Assert.assertThat(statusResult.getStdout(), containsString("UN 127.0.0.1"));
testInstance(instances, cluster.get(1));
testAllValidGetters(cluster);
testAllValidGetters(cluster, null);
}
}

View File

@ -17,53 +17,14 @@
*/
package org.apache.cassandra.distributed.test.jmx;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.management.JMRuntimeException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.JMXUtil;
import org.apache.cassandra.distributed.test.TestBaseImpl;
public class JMXGetterCheckTest extends TestBaseImpl
{
private static final Set<String> IGNORE_ATTRIBUTES = ImmutableSet.of(
"org.apache.cassandra.net:type=MessagingService:BackPressurePerHost", // throws unsupported saying the feature was removed... dropped in CASSANDRA-15375
"org.apache.cassandra.db:type=DynamicEndpointSnitch:Scores", // when running in multiple-port-one-IP mode, this fails
"org.apache.cassandra.db:type=StorageService:OutstandingSchemaVersions", // deprecated (TCM)
"org.apache.cassandra.db:type=StorageService:OutstandingSchemaVersionsWithPort" // deprecated (TCM)
);
private static final Set<String> IGNORE_OPERATIONS = ImmutableSet.of(
"org.apache.cassandra.db:type=StorageService:stopDaemon", // halts the instance, which then causes the JVM to exit
"org.apache.cassandra.db:type=StorageService:drain", // don't drain, it stops things which can cause other APIs to be unstable as we are in a stopped state
"org.apache.cassandra.db:type=StorageService:stopGossiping", // if we stop gossip this can cause other issues, so avoid
"org.apache.cassandra.db:type=StorageService:resetLocalSchema", // this will fail when there are no other nodes which can serve schema
"org.apache.cassandra.db:type=StorageService:joinRing", // Causes bootstrapping errors
"org.apache.cassandra.db:type=Tables,keyspace=system,table=local:loadNewSSTables", // Shouldn't attempt to load SSTables as sometimes the temp directories don't work
"org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache", // CIDR cache isn't enabled by default
"org.apache.cassandra.db:type=StorageService:clearConnectionHistory", // Throws a NullPointerException
"org.apache.cassandra.db:type=StorageService:startGossiping", // causes multiple loops to fail
"org.apache.cassandra.db:type=StorageService:startNativeTransport", // causes multiple loops to fail
"org.apache.cassandra.db:type=StorageService:resumeMove", // throws since there is no move in progress
"org.apache.cassandra.db:type=StorageService:abortMove", // throws since there is no move in progress
"org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache", // AllowAllCIDRAuthorizer doesn't support this operation, as feature is disabled by default
"org.apache.cassandra.db:type=StorageService:forceRemoveCompletion" // deprecated (TCM)
);
@Test
public void testGetters() throws Exception
@ -72,95 +33,8 @@ public class JMXGetterCheckTest extends TestBaseImpl
{
try (Cluster cluster = Cluster.build(1).withConfig(c -> c.with(Feature.values())).start())
{
testAllValidGetters(cluster);
JMXTestsUtil.testAllValidGetters(cluster, null);
}
}
}
/**
* Tests JMX getters and operations.
* Useful for more than just testing getters, it also is used in JMXFeatureTest
* to make sure we've touched the complete JMX code path.
* @param cluster the cluster to test
* @throws Exception several kinds of exceptions can be thrown, mostly from JMX infrastructure issues.
*/
public static void testAllValidGetters(Cluster cluster) throws Exception
{
for (IInvokableInstance instance: cluster)
{
if (instance.isShutdown())
{
continue;
}
IInstanceConfig config = instance.config();
List<Named> errors = new ArrayList<>();
try (JMXConnector jmxc = JMXUtil.getJmxConnector(config))
{
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
Set<ObjectName> metricNames = new TreeSet<>(mbsc.queryNames(null, null));
for (ObjectName name : metricNames)
{
if (!name.getDomain().startsWith("org.apache.cassandra"))
continue;
MBeanInfo info = mbsc.getMBeanInfo(name);
for (MBeanAttributeInfo a : info.getAttributes())
{
String fqn = String.format("%s:%s", name, a.getName());
if (!a.isReadable() || IGNORE_ATTRIBUTES.contains(fqn))
continue;
try
{
mbsc.getAttribute(name, a.getName());
}
catch (JMRuntimeException e)
{
errors.add(new Named(String.format("Attribute %s", fqn), e.getCause()));
}
}
for (MBeanOperationInfo o : info.getOperations())
{
String fqn = String.format("%s:%s", name, o.getName());
if (o.getSignature().length != 0 || IGNORE_OPERATIONS.contains(fqn))
continue;
try
{
mbsc.invoke(name, o.getName(), new Object[0], new String[0]);
}
catch (JMRuntimeException e)
{
errors.add(new Named(String.format("Operation %s", fqn), e.getCause()));
}
}
}
}
if (!errors.isEmpty())
{
AssertionError root = new AssertionError();
errors.forEach(root::addSuppressed);
throw root;
}
}
}
/**
* This class is meant to make new errors easier to read, by adding the JMX endpoint, and cleaning up the unneeded JMX/Reflection logic cluttering the stacktrace
*/
private static class Named extends RuntimeException
{
public Named(String msg, Throwable cause)
{
super(msg + "\nCaused by: " + cause.getClass().getCanonicalName() + ": " + cause.getMessage(), cause.getCause());
StackTraceElement[] stack = cause.getStackTrace();
List<StackTraceElement> copy = new ArrayList<>();
for (StackTraceElement s : stack)
{
if (!s.getClassName().startsWith("org.apache.cassandra"))
break;
copy.add(s);
}
Collections.reverse(copy);
setStackTrace(copy.toArray(new StackTraceElement[0]));
}
}
}

View File

@ -0,0 +1,177 @@
/*
* 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.distributed.test.jmx;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLException;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.impl.JmxTestClientSslContextFactory;
import org.apache.cassandra.distributed.impl.JmxTestClientSslSocketFactory;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.distributed.test.AbstractEncryptionOptionsImpl;
import org.apache.cassandra.utils.jmx.JMXSslPropertiesUtil;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES;
/**
* Distributed tests for JMX SSL configuration via the system properties OR the encryption options in the cassandra.yaml.
*/
public class JMXSslConfigDistributedTest extends AbstractEncryptionOptionsImpl
{
@Test
public void testDefaultEncryptionOptions() throws Throwable
{
// We must set the keystore in the system variable to make sure that the call to SSLContext.getDefault()
// uses it when Client SSL Socketfactory is initialized even if we don't need it here.
// The same default SSLContext.getDefault() will be used by other methods like testSystemSettings() in this test
// for the Server SSL Socketfactory and at that time we will need the keystore to be available
// All of the above is the issue because we run everything (JMX Server, Client) in the same JVM, multiple times
// and the SSLContext.getDefault() relies on static initialization that is reused
try (WithProperties withProperties = JMXSslPropertiesUtil.preserveAllProperties())
{
setKeystoreProperties(withProperties);
ImmutableMap<String, Object> encryptionOptionsMap = ImmutableMap.<String, Object>builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("accepted_protocols", Arrays.asList("TLSv1.2", "TLSv1.3", "TLSv1.1"))
.build();
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.JMX).set("jmx_encryption_options", encryptionOptionsMap);
}).start())
{
Map<String, Object> jmxEnv = new HashMap<>();
configureClientSocketFactory(jmxEnv, encryptionOptionsMap);
JMXTestsUtil.testAllValidGetters(cluster, jmxEnv);
}
}
}
@Test
public void testClientAuth() throws Throwable
{
try (WithProperties withProperties = JMXSslPropertiesUtil.preserveAllProperties())
{
setKeystoreProperties(withProperties);
ImmutableMap<String, Object> encryptionOptionsMap = ImmutableMap.<String, Object>builder().putAll(validFileBasedKeystores)
.put("enabled", true)
.put("require_client_auth", true)
.put("accepted_protocols", Arrays.asList("TLSv1.2", "TLSv1.3", "TLSv1.1"))
.build();
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.JMX).set("jmx_encryption_options", encryptionOptionsMap);
}).start())
{
Map<String, Object> jmxEnv = new HashMap<>();
configureClientSocketFactory(jmxEnv, encryptionOptionsMap);
JMXTestsUtil.testAllValidGetters(cluster, jmxEnv);
}
}
}
@Test
public void testSystemSettings() throws Throwable
{
COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES.reset();
try (WithProperties withProperties = JMXSslPropertiesUtil.use(true, false,
"TLSv1.2,TLSv1.3,TLSv1.1"))
{
setKeystoreProperties(withProperties);
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.JMX);
}).start())
{
Map<String, Object> jmxEnv = new HashMap<>();
SslRMIClientSocketFactory clientFactory = new SslRMIClientSocketFactory();
jmxEnv.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientFactory);
jmxEnv.put("com.sun.jndi.rmi.factory.socket", clientFactory);
JMXTestsUtil.testAllValidGetters(cluster, jmxEnv);
}
}
}
@Test
public void testInvalidKeystorePath() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.JMX).set("jmx_encryption_options",
ImmutableMap.<String, Object>builder()
.put("enabled", true)
.put("keystore", "/path/to/bad/keystore/that/should/not/exist")
.put("keystore_password", "cassandra")
.put("accepted_protocols", Arrays.asList("TLSv1.2", "TLSv1.3", "TLSv1.1"))
.build());
}).createWithoutStarting())
{
assertCannotStartDueToConfigurationException(cluster);
}
}
/**
* Tests {@code disabled} jmx_encryption_options. Here even if the configured {@code keystore} is invalid, it will
* not matter and the JMX server/client should start.
*/
@Test
public void testDisabledEncryptionOptions() throws Throwable
{
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
c.with(Feature.JMX).set("jmx_encryption_options",
ImmutableMap.builder()
.put("enabled", false)
.put("keystore", "/path/to/bad/keystore/that/should/not/exist")
.put("keystore_password", "cassandra")
.build());
}).start())
{
JMXTestsUtil.testAllValidGetters(cluster, null);
}
}
private void setKeystoreProperties(WithProperties properties)
{
properties.with("javax.net.ssl.trustStore", (String) validFileBasedKeystores.get("truststore"),
"javax.net.ssl.trustStorePassword", (String) validFileBasedKeystores.get("truststore_password"),
"javax.net.ssl.keyStore", (String) validFileBasedKeystores.get("keystore"),
"javax.net.ssl.keyStorePassword", (String) validFileBasedKeystores.get("keystore_password"));
}
@SuppressWarnings("unchecked")
private void configureClientSocketFactory(Map<String, Object> jmxEnv, Map<String, Object> encryptionOptionsMap) throws SSLException
{
JmxTestClientSslContextFactory clientSslContextFactory = new JmxTestClientSslContextFactory(encryptionOptionsMap);
List<String> cipherSuitesList = (List<String>) encryptionOptionsMap.get("cipher_suites");
String[] cipherSuites = cipherSuitesList == null ? null : cipherSuitesList.toArray(new String[0]);
List<String> acceptedProtocolList = (List<String>) encryptionOptionsMap.get("accepted_protocols");
String[] acceptedProtocols = acceptedProtocolList == null ? null : acceptedProtocolList.toArray(new String[0]);
JmxTestClientSslSocketFactory clientFactory = new JmxTestClientSslSocketFactory(clientSslContextFactory.createSSLContext(),
cipherSuites, acceptedProtocols);
jmxEnv.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, clientFactory);
jmxEnv.put("com.sun.jndi.rmi.factory.socket", clientFactory);
}
}

View File

@ -0,0 +1,160 @@
/*
* 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.distributed.test.jmx;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.management.JMRuntimeException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.JMXUtil;
public class JMXTestsUtil
{
private static final Logger logger = LoggerFactory.getLogger(JMXTestsUtil.class);
private static final Set<String> IGNORE_ATTRIBUTES = ImmutableSet.of(
"org.apache.cassandra.net:type=MessagingService:BackPressurePerHost", // throws unsupported saying the feature was removed... dropped in CASSANDRA-15375
"org.apache.cassandra.db:type=DynamicEndpointSnitch:Scores", // when running in multiple-port-one-IP mode, this fails
"org.apache.cassandra.db:type=StorageService:OutstandingSchemaVersions", // deprecated (TCM)
"org.apache.cassandra.db:type=StorageService:OutstandingSchemaVersionsWithPort" // deprecated (TCM)
);
private static final Set<String> IGNORE_OPERATIONS = ImmutableSet.of(
"org.apache.cassandra.db:type=StorageService:stopDaemon", // halts the instance, which then causes the JVM to exit
"org.apache.cassandra.db:type=StorageService:drain", // don't drain, it stops things which can cause other APIs to be unstable as we are in a stopped state
"org.apache.cassandra.db:type=StorageService:stopGossiping", // if we stop gossip this can cause other issues, so avoid
"org.apache.cassandra.db:type=StorageService:resetLocalSchema", // this will fail when there are no other nodes which can serve schema
"org.apache.cassandra.db:type=StorageService:joinRing", // Causes bootstrapping errors
"org.apache.cassandra.db:type=Tables,keyspace=system,table=local:loadNewSSTables", // Shouldn't attempt to load SSTables as sometimes the temp directories don't work
"org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache", // CIDR cache isn't enabled by default
"org.apache.cassandra.db:type=StorageService:clearConnectionHistory", // Throws a NullPointerException
"org.apache.cassandra.db:type=StorageService:startGossiping", // causes multiple loops to fail
"org.apache.cassandra.db:type=StorageService:startNativeTransport", // causes multiple loops to fail
"org.apache.cassandra.db:type=StorageService:resumeMove", // throws since there is no move in progress
"org.apache.cassandra.db:type=StorageService:abortMove", // throws since there is no move in progress
"org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache", // AllowAllCIDRAuthorizer doesn't support this operation, as feature is disabled by default
"org.apache.cassandra.db:type=StorageService:forceRemoveCompletion" // deprecated (TCM)
);
/**
* Tests JMX getters and operations and allows passing JMX Env used for the client JMX connection.
*
* @param cluster the cluster to test
* @throws Exception several kinds of exceptions can be thrown, mostly from JMX infrastructure issues.
*/
public static void testAllValidGetters(Cluster cluster, Map<String, ?> jmxEnv) throws Exception
{
for (IInvokableInstance instance : cluster)
{
if (instance.isShutdown())
{
continue;
}
IInstanceConfig config = instance.config();
List<Named> errors = new ArrayList<>();
try (JMXConnector jmxc = JMXUtil.getJmxConnector(config, jmxEnv))
{
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
Set<ObjectName> metricNames = new TreeSet<>(mbsc.queryNames(null, null));
for (ObjectName name : metricNames)
{
if (!name.getDomain().startsWith("org.apache.cassandra"))
continue;
MBeanInfo info = mbsc.getMBeanInfo(name);
for (MBeanAttributeInfo a : info.getAttributes())
{
String fqn = String.format("%s:%s", name, a.getName());
if (!a.isReadable() || IGNORE_ATTRIBUTES.contains(fqn))
continue;
try
{
mbsc.getAttribute(name, a.getName());
}
catch (JMRuntimeException e)
{
errors.add(new Named(String.format("Attribute %s", fqn), e.getCause()));
}
}
for (MBeanOperationInfo o : info.getOperations())
{
String fqn = String.format("%s:%s", name, o.getName());
if (o.getSignature().length != 0 || IGNORE_OPERATIONS.contains(fqn))
continue;
try
{
mbsc.invoke(name, o.getName(), new Object[0], new String[0]);
}
catch (JMRuntimeException e)
{
errors.add(new Named(String.format("Operation %s", fqn), e.getCause()));
}
}
}
}
if (!errors.isEmpty())
{
AssertionError root = new AssertionError();
for (Named error : errors)
{
// The Named object's message has the cause also so this only logs the message
logger.error("Error {}", error.getMessage());
root.addSuppressed(error);
}
throw root;
}
}
}
/**
* This class is meant to make new errors easier to read, by adding the JMX endpoint, and cleaning up the unneeded JMX/Reflection logic cluttering the stacktrace
*/
static class Named extends RuntimeException
{
public Named(String msg, Throwable cause)
{
super(msg + "\nCaused by: " + cause.getClass().getCanonicalName() + ": " + cause.getMessage(), cause.getCause());
StackTraceElement[] stack = cause.getStackTrace();
List<StackTraceElement> copy = new ArrayList<>();
for (StackTraceElement s : stack)
{
if (!s.getClassName().startsWith("org.apache.cassandra"))
break;
copy.add(s);
}
Collections.reverse(copy);
setStackTrace(copy.toArray(new StackTraceElement[0]));
}
}
}

View File

@ -250,7 +250,7 @@ public class EncryptionOptionsTest
for(EncryptionOptions.ConfigKey configKey: EncryptionOptions.ConfigKey.values())
{
customSslContextFactoryParams.put(configKey.getKeyName(), "my-custom-value");
customSslContextFactoryParams.put(configKey.toString(), "my-custom-value");
}
EncryptionOptionsTestCase.of(null, absentKeystore, true, customSslContextFactoryParams, ENCRYPTED);

View File

@ -40,8 +40,10 @@ public class PropertiesTest
Config config = new Config();
Set<String> keys = ImmutableSet.of("server_encryption_options.enabled",
"client_encryption_options.enabled",
"jmx_encryption_options.enabled",
"server_encryption_options.optional",
"client_encryption_options.optional");
"client_encryption_options.optional",
"jmx_encryption_options.optional");
for (Property prop : ps.values())
{
// skip these properties as they don't allow get/set within the context of this test

View File

@ -52,6 +52,7 @@ public class SettingsTableTest extends CQLTester
config = new Config();
config.client_encryption_options.applyConfig();
config.server_encryption_options.applyConfig();
config.jmx_encryption_options.applyConfig();
config.sstable_preemptive_open_interval = null;
config.index_summary_resize_interval = null;
config.cache_load_timeout = new DurationSpec.IntSecondsBound(0);

View File

@ -0,0 +1,102 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.Map;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLException;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.utils.JMXServerUtils;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
/**
* Tests for Local JMX server, the remote JMX SSL configuration via System properties in absence of jmx_encryption_options
* in the cassandra.yaml. This is the behavior before CASSANDRA-18508.
*/
public class JMXSslConfigTest
{
static WithProperties properties;
@BeforeClass
public static void setupDatabaseDescriptor()
{
properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra.yaml");
DatabaseDescriptor.daemonInitialization();
}
@AfterClass
public static void tearDownDatabaseDescriptor()
{
properties.close();
}
/**
* Tests for remote JMX SSL configuration specified via the System properties.
*/
@Test
public void testRemoteJmxSystemConfig() throws SSLException
{
InetAddress serverAddress = InetAddress.getLoopbackAddress();
String enabledProtocols = "TLSv1.2,TLSv1.3,TLSv1.1";
String cipherSuites = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
try (WithProperties ignored = JMXSslPropertiesUtil.use(true, true, enabledProtocols,
cipherSuites))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, false);
Assert.assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertTrue("RMI_SERVER_SOCKET_FACTORY must be of SslRMIServerSocketFactory type", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) instanceof SslRMIServerSocketFactory);
Assert.assertNotNull("ClientSocketFactory must not be null", env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertNotNull("com.sun.jndi.rmi.factory.socket must be set in the env", env.get("com.sun.jndi.rmi.factory.socket"));
Assert.assertEquals("protocols must match", enabledProtocols, JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertEquals("cipher-suites must match", cipherSuites, JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
/**
* Tests for {@code localOnly} JMX Server configuration.
*/
@Test
public void testLocalJmxServer() throws SSLException
{
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(false))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, true);
Assert.assertNull("ClientSocketFactory must be null", env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertNull("com.sun.jndi.rmi.factory.socket must not be set in the env", env.get("com.sun.jndi.rmi.factory.socket"));
Assert.assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertNull("protocols must be empty", JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertNull("cipher-suites must empty", JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.Map;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLException;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import org.apache.commons.lang3.StringUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.JMXServerUtils;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
/**
* Tests for Default JMX SSL configuration specified in the cassandra.yaml using jmx_encryption_options.
* The default configurtion means keystore/truststore configured with file paths and using {@code DefaultSslContextFactory}
* to initialize the SSLContext.
*/
public class JMXSslConfiguredWithYamlFileOptionsTest
{
static WithProperties properties;
@BeforeClass
public static void setupDatabaseDescriptor()
{
properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra-jmx-sslconfig.yaml");
DatabaseDescriptor.daemonInitialization();
}
@AfterClass
public static void tearDownDatabaseDescriptor()
{
properties.close();
}
@Test
public void testYamlFileJmxEncryptionOptions() throws SSLException
{
EncryptionOptions jmxEncryptionOptions = DatabaseDescriptor.getJmxEncryptionOptions();
String expectedProtocols = StringUtils.join(jmxEncryptionOptions.getAcceptedProtocols(), ",");
String expectedCipherSuites = StringUtils.join(jmxEncryptionOptions.cipherSuitesArray(), ",");
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(false))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, false);
Assert.assertTrue("com.sun.management.jmxremote.ssl must be true", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean());
Assert.assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertTrue("RMI_SERVER_SOCKET_FACTORY must be of JMXSslRMIServerSocketFactory type", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) instanceof SslRMIServerSocketFactory);
Assert.assertNotNull("ClientSocketFactory must not be null", env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertNotNull("com.sun.jndi.rmi.factory.socket must be set in the env", env.get("com.sun.jndi.rmi.factory.socket"));
Assert.assertEquals("javax.rmi.ssl.client.enabledProtocols must match", expectedProtocols, JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertEquals("javax.rmi.ssl.client.enabledCipherSuites must match", expectedCipherSuites, JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
/**
* Tests for the error scenario when the JMX SSL configuration is provided as
* system configuration as well as encryption_options.
*
* @throws SSLException
*/
@Test(expected = ConfigurationException.class)
public void testDuplicateConfig() throws SSLException
{
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(true))
{
JMXServerUtils.configureJmxSocketFactories(serverAddress, false);
}
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.Map;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.utils.JMXServerUtils;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
/**
* Tests for disabling jmx_encryption_options in the cassandra.yaml.
*/
public class JMXSslDisabledEncryptionOptionsTest
{
static WithProperties properties;
@BeforeClass
public static void setupDatabaseDescriptor()
{
properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra-jmx-disabled-sslconfig.yaml");
DatabaseDescriptor.daemonInitialization();
}
@AfterClass
public static void tearDownDatabaseDescriptor()
{
properties.close();
}
/**
* Tests disabled JMX SSL configuration. This means, all below are either not configured OR configured with
* disabled mode.
* 1. local only JMX server
* 2. System properties set for remote JMX SSL
* 3. jmx_encryption_options in the cassandra.yaml
*/
@Test
public void testDisabledJmxSslConfigs() throws SSLException
{
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(false))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, false);
Assert.assertTrue("no properties must be set", env.isEmpty());
Assert.assertFalse("com.sun.management.jmxremote.ssl must be false", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean());
Assert.assertNull("javax.rmi.ssl.client.enabledProtocols must be null", JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertNull("javax.rmi.ssl.client.enabledCipherSuites must be null", JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
/**
* Tests fallback to the `local only` JMX server when jmx_encryption_options are disabled in the cassandra.yaml
* and no System settings provided for the remote SSL config.
*/
@Test
public void testFallbackToLocalJmxServer() throws SSLException
{
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(false))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, true);
Assert.assertFalse("com.sun.management.jmxremote.ssl must be false", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean());
Assert.assertNull("com.sun.jndi.rmi.factory.socket must be null", env.get("com.sun.jndi.rmi.factory.socket"));
Assert.assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertFalse("com.sun.management.jmxremote.ssl must be false", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean());
Assert.assertNull("javax.rmi.ssl.client.enabledProtocols must be null", JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertNull("javax.rmi.ssl.client.enabledCipherSuites must be null", JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.jmx;
import java.net.InetAddress;
import java.util.Map;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLException;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import org.apache.commons.lang3.StringUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.utils.JMXServerUtils;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
/**
* Tests for JMX SSL config using keystore/truststore in the PEM format with jmx_encryption_options in the cassandra.yaml.
*/
public class JMXSslPEMConfigTest
{
static WithProperties properties;
@BeforeClass
public static void setupDatabaseDescriptor()
{
properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra-jmx-pem-sslconfig.yaml");
DatabaseDescriptor.daemonInitialization();
}
@AfterClass
public static void tearDownDatabaseDescriptor()
{
properties.close();
}
@Test
public void testPEMBasedJmxSslConfig() throws SSLException
{
EncryptionOptions jmxEncryptionOptions = DatabaseDescriptor.getJmxEncryptionOptions();
String expectedProtocols = StringUtils.join(jmxEncryptionOptions.getAcceptedProtocols(), ",");
String expectedCipherSuites = StringUtils.join(jmxEncryptionOptions.cipherSuitesArray(), ",");
InetAddress serverAddress = InetAddress.getLoopbackAddress();
try (WithProperties ignored = JMXSslPropertiesUtil.use(false))
{
Map<String, Object> env = JMXServerUtils.configureJmxSocketFactories(serverAddress, false);
Assert.assertTrue("com.sun.management.jmxremote.ssl must be true", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean());
Assert.assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertTrue("RMI_SERVER_SOCKET_FACTORY must be of JMXSslRMIServerSocketFactory type", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) instanceof SslRMIServerSocketFactory);
Assert.assertNotNull("ClientSocketFactory must not be null", env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE));
Assert.assertNotNull("com.sun.jndi.rmi.factory.socket must be set in the env", env.get("com.sun.jndi.rmi.factory.socket"));
Assert.assertEquals("javax.rmi.ssl.client.enabledProtocols must match", expectedProtocols, JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString());
Assert.assertEquals("javax.rmi.ssl.client.enabledCipherSuites must match", expectedCipherSuites, JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString());
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.jmx;
import org.apache.cassandra.distributed.shared.WithProperties;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS;
import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS;
public final class JMXSslPropertiesUtil
{
public static WithProperties use(boolean enableRemoteSsl, boolean needClientAuth, String enabledProtocols, String cipherSuites)
{
return preserveAllProperties()
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL, enableRemoteSsl)
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH, needClientAuth)
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS, enabledProtocols)
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES, cipherSuites);
}
public static WithProperties use(boolean enableRemoteSsl, boolean needClientAuth, String enabledProtocols)
{
return preserveAllProperties()
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL, enableRemoteSsl)
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH, needClientAuth)
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS, enabledProtocols);
}
public static WithProperties use(boolean enableRemoteSsl)
{
return preserveAllProperties()
.set(COM_SUN_MANAGEMENT_JMXREMOTE_SSL, enableRemoteSsl);
}
public static WithProperties preserveAllProperties()
{
return new WithProperties()
.preserve(COM_SUN_MANAGEMENT_JMXREMOTE_SSL)
.preserve(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_NEED_CLIENT_AUTH)
.preserve(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_PROTOCOLS)
.preserve(COM_SUN_MANAGEMENT_JMXREMOTE_SSL_ENABLED_CIPHER_SUITES)
.preserve(JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS)
.preserve(JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES);
}
}