mirror of https://github.com/apache/cassandra
Set right client auth for creating SSL context in mTLS optional mode
patch by Jyothsna Konisa; reviewed by Francisco Guerrero, Jon Meredith for CASSANDRA-18811
This commit is contained in:
parent
447d2bcee7
commit
bfcb21fbeb
|
|
@ -1567,6 +1567,9 @@ client_encryption_options:
|
|||
# # Must be an instance of org.apache.cassandra.security.ISslContextFactory
|
||||
# class_name: org.apache.cassandra.security.DefaultSslContextFactory
|
||||
# Verify client certificates
|
||||
# - true/REQUIRED, Verifies the client and forces the client to send client certificate
|
||||
# - false/NOT_REQUIRED, Doesn't verify the client
|
||||
# - optional, Optionally verifies the client if client certificate is sent, but doesn't force client certificate to send certificates
|
||||
require_client_auth: false
|
||||
# require_endpoint_verification: false
|
||||
# Set trustore and truststore_password if require_client_auth is true
|
||||
|
|
|
|||
|
|
@ -66,6 +66,59 @@ authenticator:
|
|||
After the bounce, C* will accept mTLS connections from the clients and if their
|
||||
identity is present in the `identity_to_roles` table, access will be granted.
|
||||
|
||||
== Configuring mTLS with password fallback authenticator for client connections
|
||||
|
||||
Operators that wish to migrate cannot immediately change the configuration to require
|
||||
mTLS authentication as it will break existing non-mTLS based clients of the cluster.
|
||||
In order to make a smooth transition from non-mTLS based authentication to mTLS authentication,
|
||||
the operator can run Cassandra in optional mTLS mode and configure authenticator to be
|
||||
`MutualTlsWithPasswordFallbackAuthenticator` which can accept both certificate based
|
||||
and password based connections.
|
||||
|
||||
Below are the steps to configure C* in optional mTLS mode with fallback authenticator.
|
||||
Note that the following steps uses SPIFFE identity as an example, If you are using
|
||||
a custom validator, use appropriate identity in place of `spiffe://testdomain.com/testIdentifier/testValue`.
|
||||
|
||||
*STEP 1: Add authorized users to system_auth.identity_to_roles table*
|
||||
|
||||
Note that only users with permissions to create/modify roles can add/remove identities.
|
||||
Client certificates with the identities in this table will be trusted by C*.
|
||||
[source, plaintext]
|
||||
----
|
||||
ADD IDENTITY 'spiffe://testdomain.com/testIdentifier/testValue' TO ROLE 'read_only_user'
|
||||
----
|
||||
|
||||
*STEP 2: Configure Cassandra.yaml with right properties*
|
||||
|
||||
`client_encryption_options` configuration for mTLS connections, Note that require_client_auth configuration
|
||||
is optional.
|
||||
[source, plaintext]
|
||||
----
|
||||
client_encryption_options:
|
||||
enabled: true
|
||||
optional: true
|
||||
keystore: conf/.keystore
|
||||
keystore_password: cassandra
|
||||
truststore: conf/.truststore
|
||||
truststore_password: cassandra
|
||||
require_client_auth: optional // to enable mTLS in optional mode
|
||||
----
|
||||
Configure fallback authenticator and the validator for client connections . If you are
|
||||
implementing a custom validator, use that instead of Spiffe validator
|
||||
[source, plaintext]
|
||||
----
|
||||
authenticator:
|
||||
class_name : org.apache.cassandra.auth.MutualTlsWithPasswordFallbackAuthenticator
|
||||
parameters :
|
||||
validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator
|
||||
----
|
||||
|
||||
*STEP 3: Bounce the cluster*
|
||||
|
||||
After the bounce, C* will accept both mTLS connections and password based connections from
|
||||
the clients. This configuration should be used during transition phase and the require_client_auth
|
||||
configuration should be set to true when all the clients start making mTLS connections to the cluster.
|
||||
|
||||
== Configuring mTLS authenticator for Internode connections
|
||||
|
||||
Internode authenticator trusts certificates whose identities are present in
|
||||
|
|
|
|||
|
|
@ -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", Boolean.FALSE);
|
||||
commonConfig.put("require_client_auth", "false";
|
||||
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public class KubernetesSecretsSslContextFactoryTest
|
|||
* done to avoid having a dependency on env in the unit test.
|
||||
*/
|
||||
commonConfig.put("MY_TRUSTSTORE_PASSWORD", "cassandra");
|
||||
commonConfig.put("require_client_auth", Boolean.FALSE);
|
||||
commonConfig.put("require_client_auth", "false");
|
||||
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ import org.apache.cassandra.exceptions.ConfigurationException;
|
|||
import org.apache.cassandra.schema.SchemaConstants;
|
||||
import org.apache.cassandra.utils.NoSpamLogger;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
/*
|
||||
* Performs mTLS authentication for client connections by extracting identities from client certificate
|
||||
* and verifying them against the authorized identities in IdentityCache. IdentityCache is a loading cache that
|
||||
|
|
@ -79,7 +81,6 @@ public class MutualTlsAuthenticator implements IAuthenticator
|
|||
}
|
||||
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()));
|
||||
checkMtlsConfigurationIsValid(DatabaseDescriptor.getRawConfig());
|
||||
AuthCacheService.instance.register(identityCache);
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +99,14 @@ public class MutualTlsAuthenticator implements IAuthenticator
|
|||
@Override
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
if (!config.client_encryption_options.getEnabled() || config.client_encryption_options.getClientAuth() != REQUIRED)
|
||||
{
|
||||
String msg = "MutualTlsAuthenticator requires client_encryption_options.enabled to be true" +
|
||||
" & client_encryption_options.require_client_auth to be true";
|
||||
logger.error(msg);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,17 +183,6 @@ public class MutualTlsAuthenticator implements IAuthenticator
|
|||
}
|
||||
}
|
||||
|
||||
private void checkMtlsConfigurationIsValid(Config config)
|
||||
{
|
||||
if (!config.client_encryption_options.getEnabled() || !config.client_encryption_options.require_client_auth)
|
||||
{
|
||||
String msg = "MutualTlsAuthenticator requires client_encryption_options.enabled to be true" +
|
||||
" & client_encryption_options.require_client_auth to be true";
|
||||
logger.error(msg);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static class IdentityCache extends AuthCache<String, String>
|
||||
{
|
||||
IdentityCache()
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ import org.apache.cassandra.exceptions.AuthenticationException;
|
|||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.utils.NoSpamLogger;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
/*
|
||||
* Performs mTLS authentication for internode connections by extracting identities from the certificates of incoming
|
||||
* connection and verifying them against a list of authorized peers. Authorized peers can be configured in
|
||||
|
|
@ -89,7 +91,6 @@ public class MutualTlsInternodeAuthenticator implements IInternodeAuthenticator
|
|||
certificateValidator = ParameterizedClass.newInstance(new ParameterizedClass(certificateValidatorClassName),
|
||||
Arrays.asList("", AuthConfig.class.getPackage().getName()));
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
checkInternodeMtlsConfigurationIsValid(config);
|
||||
|
||||
if (parameters.containsKey(TRUSTED_PEER_IDENTITIES))
|
||||
{
|
||||
|
|
@ -146,6 +147,15 @@ public class MutualTlsInternodeAuthenticator implements IInternodeAuthenticator
|
|||
@Override
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
if (config.server_encryption_options.internode_encryption == EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.none
|
||||
|| config.server_encryption_options.getClientAuth() != REQUIRED)
|
||||
{
|
||||
String msg = "MutualTlsInternodeAuthenticator requires server_encryption_options.internode_encryption to be enabled" +
|
||||
" & server_encryption_options.require_client_auth to be true";
|
||||
logger.error(msg);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -212,15 +222,4 @@ public class MutualTlsInternodeAuthenticator implements IInternodeAuthenticator
|
|||
return allUsers;
|
||||
}
|
||||
|
||||
private void checkInternodeMtlsConfigurationIsValid(Config config)
|
||||
{
|
||||
if (config.server_encryption_options.internode_encryption == EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.none
|
||||
|| !config.server_encryption_options.require_client_auth)
|
||||
{
|
||||
String msg = "MutualTlsInternodeAuthenticator requires server_encryption_options.internode_encryption to be enabled" +
|
||||
" & server_encryption_options.require_client_auth to be true";
|
||||
logger.error(msg);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ import java.net.InetAddress;
|
|||
import java.security.cert.Certificate;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
|
||||
/**
|
||||
* This authenticator can be used in optional mTLS mode, If the client doesn't make an mTLS connection
|
||||
* this fallbacks to password authentication.
|
||||
|
|
@ -50,4 +55,15 @@ public class MutualTlsWithPasswordFallbackAuthenticator extends PasswordAuthenti
|
|||
}
|
||||
return mutualTlsAuthenticator.newSaslNegotiator(clientAddress, certificates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
if (config.client_encryption_options.getClientAuth() == EncryptionOptions.ClientAuth.NOT_REQUIRED)
|
||||
{
|
||||
String msg = "MutualTlsWithPasswordFallbackAuthenticator requires client_encryption_options.require_client_auth to be optional/true";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.UNSAFE_SYS
|
|||
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.BYTES_PER_SECOND;
|
||||
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND;
|
||||
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.MEBIBYTES;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ALL;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.EACH_QUORUM;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_QUORUM;
|
||||
|
|
@ -1241,8 +1242,8 @@ public class DatabaseDescriptor
|
|||
|
||||
try
|
||||
{
|
||||
SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, true, true);
|
||||
SSLFactory.validateSslContext("Native transport", conf.client_encryption_options, conf.client_encryption_options.require_client_auth, true);
|
||||
SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, REQUIRED, true);
|
||||
SSLFactory.validateSslContext("Native transport", conf.client_encryption_options, conf.client_encryption_options.getClientAuth(), true);
|
||||
SSLFactory.initHotReloading(conf.server_encryption_options, conf.client_encryption_options, false);
|
||||
}
|
||||
catch (IOException e)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.locator.IEndpointSnitch;
|
||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||
import org.apache.cassandra.security.DisableSslContextFactory;
|
||||
|
|
@ -67,6 +68,42 @@ public class EncryptionOptions
|
|||
}
|
||||
}
|
||||
|
||||
public enum ClientAuth
|
||||
{
|
||||
REQUIRED("true"),
|
||||
NOT_REQUIRED("false"),
|
||||
OPTIONAL("optional");
|
||||
private final String value;
|
||||
private static final Map<String, ClientAuth> VALUES = new HashMap<>();
|
||||
static
|
||||
{
|
||||
for (ClientAuth clientAuth : ClientAuth.values())
|
||||
{
|
||||
VALUES.put(clientAuth.value, clientAuth);
|
||||
VALUES.put(clientAuth.name().toLowerCase(), clientAuth);
|
||||
}
|
||||
}
|
||||
|
||||
ClientAuth(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ClientAuth from(String value)
|
||||
{
|
||||
if (VALUES.containsKey(value.toLowerCase()))
|
||||
{
|
||||
return VALUES.get(value.toLowerCase());
|
||||
}
|
||||
throw new ConfigurationException(value + " is not a valid ClientAuth option");
|
||||
}
|
||||
|
||||
public String value()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the ssl_context_factory is configured, most likely it won't use file based keystores and truststores and
|
||||
* can choose to completely customize SSL context's creation. Most likely it won't also use keystore_password and
|
||||
|
|
@ -84,7 +121,7 @@ public class EncryptionOptions
|
|||
protected List<String> accepted_protocols;
|
||||
public final String algorithm;
|
||||
public final String store_type;
|
||||
public final boolean require_client_auth;
|
||||
public final String require_client_auth;
|
||||
public final boolean require_endpoint_verification;
|
||||
// ServerEncryptionOptions does not use the enabled flag at all instead using the existing
|
||||
// internode_encryption option. So we force this private and expose through isEnabled
|
||||
|
|
@ -159,7 +196,7 @@ public class EncryptionOptions
|
|||
accepted_protocols = null;
|
||||
algorithm = null;
|
||||
store_type = "JKS";
|
||||
require_client_auth = false;
|
||||
require_client_auth = "false";
|
||||
require_endpoint_verification = false;
|
||||
enabled = null;
|
||||
optional = null;
|
||||
|
|
@ -168,7 +205,7 @@ public class EncryptionOptions
|
|||
public EncryptionOptions(ParameterizedClass ssl_context_factory, String keystore, String keystore_password,
|
||||
String truststore, String truststore_password, List<String> cipher_suites,
|
||||
String protocol, List<String> accepted_protocols, String algorithm, String store_type,
|
||||
boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled,
|
||||
String require_client_auth, boolean require_endpoint_verification, Boolean enabled,
|
||||
Boolean optional)
|
||||
{
|
||||
this.ssl_context_factory = ssl_context_factory;
|
||||
|
|
@ -402,6 +439,11 @@ public class EncryptionOptions
|
|||
return sslContextFactoryInstance == null ? null : sslContextFactoryInstance.getAcceptedProtocols();
|
||||
}
|
||||
|
||||
public ClientAuth getClientAuth()
|
||||
{
|
||||
return this.require_client_auth == null ? ClientAuth.NOT_REQUIRED : ClientAuth.from(this.require_client_auth);
|
||||
}
|
||||
|
||||
public String[] acceptedProtocolsArray()
|
||||
{
|
||||
List<String> ap = getAcceptedProtocols();
|
||||
|
|
@ -520,11 +562,11 @@ public class EncryptionOptions
|
|||
optional).applyConfig();
|
||||
}
|
||||
|
||||
public EncryptionOptions withRequireClientAuth(boolean require_client_auth)
|
||||
public EncryptionOptions withRequireClientAuth(ClientAuth require_client_auth)
|
||||
{
|
||||
return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore,
|
||||
truststore_password, cipher_suites, protocol, accepted_protocols, algorithm,
|
||||
store_type, require_client_auth, require_endpoint_verification, enabled,
|
||||
store_type, require_client_auth.value, require_endpoint_verification, enabled,
|
||||
optional).applyConfig();
|
||||
}
|
||||
|
||||
|
|
@ -567,7 +609,7 @@ public class EncryptionOptions
|
|||
EncryptionOptions opt = (EncryptionOptions)o;
|
||||
return enabled == opt.enabled &&
|
||||
optional == opt.optional &&
|
||||
require_client_auth == opt.require_client_auth &&
|
||||
require_client_auth.equals(opt.require_client_auth) &&
|
||||
require_endpoint_verification == opt.require_endpoint_verification &&
|
||||
Objects.equals(keystore, opt.keystore) &&
|
||||
Objects.equals(keystore_password, opt.keystore_password) &&
|
||||
|
|
@ -600,7 +642,7 @@ public class EncryptionOptions
|
|||
result += 31 * (enabled == null ? 0 : Boolean.hashCode(enabled));
|
||||
result += 31 * (optional == null ? 0 : Boolean.hashCode(optional));
|
||||
result += 31 * (cipher_suites == null ? 0 : cipher_suites.hashCode());
|
||||
result += 31 * Boolean.hashCode(require_client_auth);
|
||||
result += 31 * require_client_auth.hashCode();
|
||||
result += 31 * Boolean.hashCode(require_endpoint_verification);
|
||||
result += 31 * (ssl_context_factory == null ? 0 : ssl_context_factory.hashCode());
|
||||
return result;
|
||||
|
|
@ -632,7 +674,7 @@ public class EncryptionOptions
|
|||
String keystore_password,String outbound_keystore,
|
||||
String outbound_keystore_password, String truststore, String truststore_password,
|
||||
List<String> cipher_suites, String protocol, List<String> accepted_protocols,
|
||||
String algorithm, String store_type, boolean require_client_auth,
|
||||
String algorithm, String store_type, String require_client_auth,
|
||||
boolean require_endpoint_verification, Boolean optional,
|
||||
InternodeEncryption internode_encryption, boolean legacy_ssl_storage_port_enabled)
|
||||
{
|
||||
|
|
@ -679,7 +721,7 @@ public class EncryptionOptions
|
|||
logger.warn("Setting server_encryption_options.enabled has no effect, use internode_encryption");
|
||||
}
|
||||
|
||||
if (require_client_auth && (internode_encryption == InternodeEncryption.rack || internode_encryption == InternodeEncryption.dc))
|
||||
if (getClientAuth() != ClientAuth.NOT_REQUIRED && (internode_encryption == InternodeEncryption.rack || internode_encryption == InternodeEncryption.dc))
|
||||
{
|
||||
logger.warn("Setting require_client_auth is incompatible with 'rack' and 'dc' internode_encryption values."
|
||||
+ " It is possible for an internode connection to pretend to be in the same rack/dc by spoofing"
|
||||
|
|
@ -875,12 +917,12 @@ public class EncryptionOptions
|
|||
legacy_ssl_storage_port_enabled).applyConfigInternal();
|
||||
}
|
||||
|
||||
public ServerEncryptionOptions withRequireClientAuth(boolean require_client_auth)
|
||||
public ServerEncryptionOptions withRequireClientAuth(ClientAuth require_client_auth)
|
||||
{
|
||||
return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password,
|
||||
outbound_keystore, outbound_keystore_password, truststore,
|
||||
truststore_password, cipher_suites, protocol, accepted_protocols,
|
||||
algorithm, store_type, require_client_auth,
|
||||
algorithm, store_type, require_client_auth.value,
|
||||
require_endpoint_verification, optional, internode_encryption,
|
||||
legacy_ssl_storage_port_enabled).applyConfigInternal();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import static java.lang.Math.*;
|
|||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.INBOUND;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.DISCARD_HANDLER_NAME;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_FACTORY_CONTEXT_DESCRIPTION;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_HANDLER_NAME;
|
||||
|
|
@ -521,7 +522,7 @@ public class InboundConnectionInitiator
|
|||
|
||||
private static SslHandler getSslHandler(String description, Channel channel, EncryptionOptions.ServerEncryptionOptions encryptionOptions) throws IOException
|
||||
{
|
||||
final boolean verifyPeerCertificate = true;
|
||||
final EncryptionOptions.ClientAuth verifyPeerCertificate = REQUIRED;
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, verifyPeerCertificate,
|
||||
ISslContextFactory.SocketType.SERVER,
|
||||
SSL_FACTORY_CONTEXT_DESCRIPTION);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
|
||||
import io.netty.util.concurrent.Future; //checkstyle: permit this import
|
||||
import io.netty.util.concurrent.Promise; //checkstyle: permit this import
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -65,6 +66,9 @@ import org.apache.cassandra.utils.memory.BufferPools;
|
|||
import static java.util.concurrent.TimeUnit.*;
|
||||
import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.OUTBOUND;
|
||||
import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.OUTBOUND_PRECONNECT;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.OPTIONAL;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.DISCARD_HANDLER_NAME;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_FACTORY_CONTEXT_DESCRIPTION;
|
||||
import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_HANDLER_NAME;
|
||||
|
|
@ -241,14 +245,18 @@ public class OutboundConnectionInitiator<SuccessType extends OutboundConnectionI
|
|||
|
||||
private SslContext getSslContext(SslFallbackConnectionType connectionType) throws IOException
|
||||
{
|
||||
boolean requireClientAuth = false;
|
||||
if (connectionType == SslFallbackConnectionType.MTLS || connectionType == SslFallbackConnectionType.SSL)
|
||||
EncryptionOptions.ClientAuth requireClientAuth = NOT_REQUIRED;
|
||||
if (connectionType == SslFallbackConnectionType.MTLS )
|
||||
{
|
||||
requireClientAuth = true;
|
||||
requireClientAuth = REQUIRED;
|
||||
}
|
||||
else if(connectionType == SslFallbackConnectionType.SSL)
|
||||
{
|
||||
requireClientAuth = OPTIONAL;
|
||||
}
|
||||
else if (connectionType == SslFallbackConnectionType.SERVER_CONFIG)
|
||||
{
|
||||
requireClientAuth = settings.withEncryption();
|
||||
requireClientAuth = settings.withEncryption() ? REQUIRED: NOT_REQUIRED;
|
||||
}
|
||||
return SSLFactory.getOrCreateSslContext(settings.encryption, requireClientAuth, ISslContextFactory.SocketType.CLIENT, SSL_FACTORY_CONTEXT_DESCRIPTION);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ import io.netty.handler.ssl.OpenSsl;
|
|||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_TCACTIVE_OPENSSL;
|
||||
|
||||
|
|
@ -64,7 +68,7 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
protected final List<String> accepted_protocols;
|
||||
protected final String algorithm;
|
||||
protected final String store_type;
|
||||
protected final boolean require_client_auth;
|
||||
protected final EncryptionOptions.ClientAuth clientAuth;
|
||||
protected final boolean require_endpoint_verification;
|
||||
/*
|
||||
ServerEncryptionOptions does not use the enabled flag at all instead using the existing
|
||||
|
|
@ -86,7 +90,7 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
accepted_protocols = null;
|
||||
algorithm = null;
|
||||
store_type = "JKS";
|
||||
require_client_auth = false;
|
||||
clientAuth = NOT_REQUIRED;
|
||||
require_endpoint_verification = false;
|
||||
enabled = null;
|
||||
optional = null;
|
||||
|
|
@ -101,7 +105,7 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
accepted_protocols = getStringList("accepted_protocols");
|
||||
algorithm = getString("algorithm");
|
||||
store_type = getString("store_type", "JKS");
|
||||
require_client_auth = getBoolean("require_client_auth", false);
|
||||
clientAuth = parameters.get("require_client_auth") == null ? NOT_REQUIRED : EncryptionOptions.ClientAuth.from(getString("require_client_auth"));
|
||||
require_endpoint_verification = getBoolean("require_endpoint_verification", false);
|
||||
enabled = getBoolean("enabled");
|
||||
optional = getBoolean("optional");
|
||||
|
|
@ -149,9 +153,15 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
|
||||
@Override
|
||||
public SSLContext createJSSESslContext(boolean verifyPeerCertificate) throws SSLException
|
||||
{
|
||||
return createJSSESslContext(verifyPeerCertificate ? REQUIRED : NOT_REQUIRED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLContext createJSSESslContext(EncryptionOptions.ClientAuth clientAuth) throws SSLException
|
||||
{
|
||||
TrustManager[] trustManagers = null;
|
||||
if (verifyPeerCertificate)
|
||||
if (clientAuth != NOT_REQUIRED)
|
||||
trustManagers = buildTrustManagerFactory().getTrustManagers();
|
||||
|
||||
KeyManagerFactory kmf = buildKeyManagerFactory();
|
||||
|
|
@ -171,6 +181,13 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
@Override
|
||||
public SslContext createNettySslContext(boolean verifyPeerCertificate, SocketType socketType,
|
||||
CipherSuiteFilter cipherFilter) throws SSLException
|
||||
{
|
||||
return createNettySslContext(verifyPeerCertificate ? REQUIRED : NOT_REQUIRED, socketType, cipherFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslContext createNettySslContext(EncryptionOptions.ClientAuth clientAuth, SocketType socketType,
|
||||
CipherSuiteFilter cipherFilter) throws SSLException
|
||||
{
|
||||
/*
|
||||
There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
|
||||
|
|
@ -184,8 +201,7 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
if (socketType == SocketType.SERVER)
|
||||
{
|
||||
KeyManagerFactory kmf = buildKeyManagerFactory();
|
||||
builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
|
||||
ClientAuth.NONE);
|
||||
builder = SslContextBuilder.forServer(kmf).clientAuth(toNettyClientAuth(this.clientAuth));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -200,7 +216,7 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
if (cipher_suites != null && !cipher_suites.isEmpty())
|
||||
builder.ciphers(cipher_suites, cipherFilter);
|
||||
|
||||
if (verifyPeerCertificate)
|
||||
if (clientAuth != NOT_REQUIRED)
|
||||
builder.trustManager(buildTrustManagerFactory());
|
||||
|
||||
return builder.build();
|
||||
|
|
@ -274,4 +290,19 @@ abstract public class AbstractSslContextFactory implements ISslContextFactory
|
|||
* @throws SSLException
|
||||
*/
|
||||
abstract protected KeyManagerFactory buildOutboundKeyManagerFactory() throws SSLException;
|
||||
|
||||
private ClientAuth toNettyClientAuth(EncryptionOptions.ClientAuth clientAuth)
|
||||
{
|
||||
switch (clientAuth)
|
||||
{
|
||||
case REQUIRED:
|
||||
return ClientAuth.REQUIRE;
|
||||
case NOT_REQUIRED:
|
||||
return ClientAuth.NONE;
|
||||
case OPTIONAL:
|
||||
return ClientAuth.OPTIONAL;
|
||||
default:
|
||||
throw new RuntimeException("Unsupported client auth " + clientAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import javax.net.ssl.SSLException;
|
|||
|
||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
|
||||
/**
|
||||
* The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
|
||||
|
|
@ -57,9 +58,33 @@ public interface ISslContextFactory
|
|||
* @param verifyPeerCertificate {@code true} if SSL peer's certificate needs to be verified; {@code false} otherwise
|
||||
* @return JSSE's {@link SSLContext}
|
||||
* @throws SSLException in case the Ssl Context creation fails for some reason
|
||||
*
|
||||
* @deprecated See CASSANDRA-18811
|
||||
*/
|
||||
@Deprecated(since = "5.1")
|
||||
SSLContext createJSSESslContext(boolean verifyPeerCertificate) throws SSLException;
|
||||
|
||||
/**
|
||||
* Creates JSSE SSLContext.
|
||||
*
|
||||
* @param clientAuth {@code REQUIRED} if SSL peer's certificate needs to be verified; {@code OPTIONAL} if peer's
|
||||
* certificate needs to be optionally verfied; {@code NOT_REQUIRED} otherwise.
|
||||
* @return JSSE's {@link SSLContext}
|
||||
* @throws SSLException in case the Ssl Context creation fails for some reason
|
||||
*/
|
||||
default SSLContext createJSSESslContext(EncryptionOptions.ClientAuth clientAuth) throws SSLException
|
||||
{
|
||||
switch (clientAuth)
|
||||
{
|
||||
case REQUIRED:
|
||||
return createJSSESslContext(true);
|
||||
case NOT_REQUIRED:
|
||||
case OPTIONAL:
|
||||
default:
|
||||
return createJSSESslContext(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Netty's SslContext object.
|
||||
*
|
||||
|
|
@ -69,10 +94,38 @@ public interface ISslContextFactory
|
|||
* {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
|
||||
* @return Netty's {@link SslContext}
|
||||
* @throws SSLException in case the Ssl Context creation fails for some reason
|
||||
*
|
||||
* @deprecated See CASSANDRA-18811
|
||||
*/
|
||||
@Deprecated(since = "5.1")
|
||||
SslContext createNettySslContext(boolean verifyPeerCertificate, SocketType socketType,
|
||||
CipherSuiteFilter cipherFilter) throws SSLException;
|
||||
|
||||
/**
|
||||
* Creates Netty's SslContext object.
|
||||
*
|
||||
* @param clientAuth {@code REQUIRED} if SSL peer's certificate needs to be verified; {@code OPTIONAL} if peer's
|
||||
* * certificate needs to be optionally verfied; {@code NOT_REQUIRED} otherwise.
|
||||
* @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
|
||||
* @param cipherFilter to allow Netty's cipher suite filtering, e.g.
|
||||
* {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
|
||||
* @return Netty's {@link SslContext}
|
||||
* @throws SSLException in case the Ssl Context creation fails for some reason
|
||||
*/
|
||||
default SslContext createNettySslContext(EncryptionOptions.ClientAuth clientAuth, SocketType socketType,
|
||||
CipherSuiteFilter cipherFilter) throws SSLException
|
||||
{
|
||||
switch (clientAuth)
|
||||
{
|
||||
case REQUIRED:
|
||||
return createNettySslContext(true, socketType, cipherFilter);
|
||||
case NOT_REQUIRED:
|
||||
case OPTIONAL:
|
||||
default:
|
||||
return createNettySslContext(false, socketType, cipherFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ import org.apache.cassandra.security.ISslContextFactory.SocketType;
|
|||
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_TCACTIVE_OPENSSL;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
/**
|
||||
* A Factory for providing and setting up client {@link SSLSocket}s. Also provides
|
||||
* methods for creating both JSSE {@link SSLContext} instances as well as netty {@link SslContext} instances.
|
||||
|
|
@ -122,15 +124,15 @@ public final class SSLFactory
|
|||
/**
|
||||
* Create a JSSE {@link SSLContext}.
|
||||
*/
|
||||
public static SSLContext createSSLContext(EncryptionOptions options, boolean verifyPeerCertificate) throws IOException
|
||||
public static SSLContext createSSLContext(EncryptionOptions options, EncryptionOptions.ClientAuth clientAuth) throws IOException
|
||||
{
|
||||
return options.sslContextFactoryInstance.createJSSESslContext(verifyPeerCertificate);
|
||||
return options.sslContextFactoryInstance.createJSSESslContext(clientAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a netty {@link SslContext} instance
|
||||
*/
|
||||
public static SslContext getOrCreateSslContext(EncryptionOptions options, boolean verifyPeerCertificate,
|
||||
public static SslContext getOrCreateSslContext(EncryptionOptions options, EncryptionOptions.ClientAuth clientAuth,
|
||||
SocketType socketType,
|
||||
String contextDescription) throws IOException
|
||||
{
|
||||
|
|
@ -141,7 +143,7 @@ public final class SSLFactory
|
|||
if (sslContext != null)
|
||||
return sslContext;
|
||||
|
||||
sslContext = createNettySslContext(options, verifyPeerCertificate, socketType);
|
||||
sslContext = createNettySslContext(options, clientAuth, socketType);
|
||||
|
||||
SslContext previous = cachedSslContexts.putIfAbsent(key, sslContext);
|
||||
if (previous == null)
|
||||
|
|
@ -154,20 +156,20 @@ public final class SSLFactory
|
|||
/**
|
||||
* Create a Netty {@link SslContext}
|
||||
*/
|
||||
static SslContext createNettySslContext(EncryptionOptions options, boolean verifyPeerCertificate,
|
||||
static SslContext createNettySslContext(EncryptionOptions options, EncryptionOptions.ClientAuth clientAuth,
|
||||
SocketType socketType) throws IOException
|
||||
{
|
||||
return createNettySslContext(options, verifyPeerCertificate, socketType,
|
||||
return createNettySslContext(options, clientAuth, socketType,
|
||||
LoggingCipherSuiteFilter.QUIET_FILTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Netty {@link SslContext} with a supplied cipherFilter
|
||||
*/
|
||||
static SslContext createNettySslContext(EncryptionOptions options, boolean verifyPeerCertificate,
|
||||
static SslContext createNettySslContext(EncryptionOptions options, EncryptionOptions.ClientAuth clientAuth,
|
||||
SocketType socketType, CipherSuiteFilter cipherFilter) throws IOException
|
||||
{
|
||||
return options.sslContextFactoryInstance.createNettySslContext(verifyPeerCertificate, socketType,
|
||||
return options.sslContextFactoryInstance.createNettySslContext(clientAuth, socketType,
|
||||
cipherFilter);
|
||||
}
|
||||
|
||||
|
|
@ -206,7 +208,7 @@ public final class SSLFactory
|
|||
try
|
||||
{
|
||||
validateSslContext(key.contextDescription, opts,
|
||||
opts instanceof EncryptionOptions.ServerEncryptionOptions || opts.require_client_auth, false);
|
||||
opts instanceof EncryptionOptions.ServerEncryptionOptions? REQUIRED : opts.getClientAuth(), false);
|
||||
logger.info("SSL certificates have been updated for {}. Resetting the ssl contexts for new " +
|
||||
"connections.", key.contextDescription);
|
||||
clearSslContextCache(key.encryptionOptions, keysToCheck);
|
||||
|
|
@ -353,7 +355,7 @@ public final class SSLFactory
|
|||
return !string.equals("SSLv2Hello");
|
||||
}
|
||||
|
||||
public static void validateSslContext(String contextDescription, EncryptionOptions options, boolean verifyPeerCertificate, boolean logProtocolAndCiphers) throws IOException
|
||||
public static void validateSslContext(String contextDescription, EncryptionOptions options, EncryptionOptions.ClientAuth clientAuth, boolean logProtocolAndCiphers) throws IOException
|
||||
{
|
||||
if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
|
||||
{
|
||||
|
|
@ -361,7 +363,7 @@ public final class SSLFactory
|
|||
{
|
||||
CipherSuiteFilter loggingCipherSuiteFilter = logProtocolAndCiphers ? new LoggingCipherSuiteFilter(contextDescription)
|
||||
: LoggingCipherSuiteFilter.QUIET_FILTER;
|
||||
SslContext serverSslContext = createNettySslContext(options, verifyPeerCertificate, SocketType.SERVER, loggingCipherSuiteFilter);
|
||||
SslContext serverSslContext = createNettySslContext(options, clientAuth, SocketType.SERVER, loggingCipherSuiteFilter);
|
||||
try
|
||||
{
|
||||
SSLEngine engine = serverSslContext.newEngine(ByteBufAllocator.DEFAULT);
|
||||
|
|
@ -406,7 +408,7 @@ public final class SSLFactory
|
|||
}
|
||||
|
||||
// Make sure it is possible to build the client context too
|
||||
SslContext clientSslContext = createNettySslContext(options, verifyPeerCertificate, SocketType.CLIENT);
|
||||
SslContext clientSslContext = createNettySslContext(options, clientAuth, SocketType.CLIENT);
|
||||
ReferenceCountUtil.release(clientSslContext);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -421,8 +423,8 @@ public final class SSLFactory
|
|||
*/
|
||||
public static void validateSslCerts(EncryptionOptions.ServerEncryptionOptions serverOpts, EncryptionOptions clientOpts) throws IOException
|
||||
{
|
||||
validateSslContext("server_encryption_options", serverOpts, true, false);
|
||||
validateSslContext("client_encryption_options", clientOpts, clientOpts.require_client_auth, false);
|
||||
validateSslContext("server_encryption_options", serverOpts, REQUIRED, false);
|
||||
validateSslContext("client_encryption_options", clientOpts, clientOpts.getClientAuth(), false);
|
||||
}
|
||||
|
||||
static class CacheKey
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import org.apache.cassandra.utils.JVMStabilityInspector;
|
|||
import org.apache.cassandra.utils.NativeSSTableLoaderClient;
|
||||
import org.apache.cassandra.utils.OutputHandler;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
|
||||
public class BulkLoader
|
||||
|
|
@ -264,7 +265,7 @@ public class BulkLoader
|
|||
SSLContext sslContext;
|
||||
try
|
||||
{
|
||||
sslContext = SSLFactory.createSSLContext(clientEncryptionOptions, true);
|
||||
sslContext = SSLFactory.createSSLContext(clientEncryptionOptions, REQUIRED);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import org.apache.cassandra.locator.InetAddressAndPort;
|
|||
import org.apache.cassandra.tools.BulkLoader.CmdLineOptions;
|
||||
|
||||
import static org.apache.cassandra.config.DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
public class LoaderOptions
|
||||
{
|
||||
|
|
@ -648,7 +649,7 @@ public class LoaderOptions
|
|||
{
|
||||
// if a keystore was provided, lets assume we'll need to use
|
||||
clientEncOptions = clientEncOptions.withKeyStore(cmd.getOptionValue(SSL_KEYSTORE))
|
||||
.withRequireClientAuth(true);
|
||||
.withRequireClientAuth(REQUIRED);
|
||||
}
|
||||
|
||||
if (cmd.hasOption(SSL_KEYSTORE_PW))
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ public class PipelineConfigurator
|
|||
logger.debug("Enabling optionally encrypted CQL connections between client and server");
|
||||
return channel -> {
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions,
|
||||
encryptionOptions.require_client_auth,
|
||||
encryptionOptions.getClientAuth(),
|
||||
ISslContextFactory.SocketType.SERVER,
|
||||
SSL_FACTORY_CONTEXT_DESCRIPTION);
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ public class PipelineConfigurator
|
|||
logger.debug("Enabling encrypted CQL connections between client and server");
|
||||
return channel -> {
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions,
|
||||
encryptionOptions.require_client_auth,
|
||||
encryptionOptions.getClientAuth(),
|
||||
ISslContextFactory.SocketType.SERVER,
|
||||
SSL_FACTORY_CONTEXT_DESCRIPTION);
|
||||
InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null;
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ public class SimpleClient implements Closeable
|
|||
protected void initChannel(Channel channel) throws Exception
|
||||
{
|
||||
super.initChannel(channel);
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, encryptionOptions.require_client_auth,
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, encryptionOptions.getClientAuth(),
|
||||
ISslContextFactory.SocketType.CLIENT, SSL_FACTORY_CONTEXT_DESCRIPTION);
|
||||
InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? new InetSocketAddress(host, port) : null;
|
||||
channel.pipeline().addFirst("ssl", newSslHandler(channel, sslContext, peer));
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import org.apache.cassandra.security.ISslContextFactory;
|
|||
import org.apache.cassandra.security.SSLFactory;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.distributed.test.AbstractEncryptionOptionsImpl.ConnectResult.CONNECTING;
|
||||
import static org.apache.cassandra.distributed.test.AbstractEncryptionOptionsImpl.ConnectResult.UNINITIALIZED;
|
||||
import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition;
|
||||
|
|
@ -199,8 +200,8 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
|
|||
setProtocolAndCipher(null, null);
|
||||
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(
|
||||
encryptionOptions.withAcceptedProtocols(acceptedProtocols).withCipherSuites(cipherSuites),
|
||||
true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
encryptionOptions.withAcceptedProtocols(acceptedProtocols).withCipherSuites(cipherSuites),
|
||||
REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
Bootstrap b = new Bootstrap();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ import com.datastax.shaded.netty.handler.ssl.SslContextBuilder;
|
|||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.Feature;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOptionsImpl
|
||||
{
|
||||
@Rule
|
||||
|
|
@ -278,6 +281,138 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
|
|||
testEndpointVerification(true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalMtlsModeAllowNonSSLConnections() throws Exception
|
||||
{
|
||||
// When server is configured in optional mTLS mode and allowing ssl connections, it should accept
|
||||
// non-ssl, ssl, mTLS based connections
|
||||
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
|
||||
c.with(Feature.NATIVE_PROTOCOL);
|
||||
c.set("client_encryption_options",
|
||||
ImmutableMap.builder().putAll(validKeystore)
|
||||
.put("enabled", true)
|
||||
.put("require_client_auth", "optional")
|
||||
.put("optional", true)
|
||||
.build());
|
||||
}).start())
|
||||
{
|
||||
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
|
||||
|
||||
// non-ssl connections should succeed
|
||||
com.datastax.driver.core.Cluster nonSSLDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.build();
|
||||
assertNotNull(nonSSLDriver.connect());
|
||||
|
||||
// ssl connections should succeed
|
||||
com.datastax.driver.core.Cluster sslDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(false))
|
||||
.build();
|
||||
assertNotNull(sslDriver.connect());
|
||||
|
||||
// mtls connections should succeed
|
||||
com.datastax.driver.core.Cluster mtlsDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(true))
|
||||
.build();
|
||||
assertNotNull(mtlsDriver.connect());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalMtlsModeDoNotAllowNonSSLConnections() throws Exception
|
||||
{
|
||||
// When server is configured in optional mTLS mode restricting non-ssl connections, it should accept
|
||||
// ssl, mTLS based connections and reject any non-ssl based connections
|
||||
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
|
||||
c.with(Feature.NATIVE_PROTOCOL);
|
||||
c.set("client_encryption_options",
|
||||
ImmutableMap.builder().putAll(validKeystore)
|
||||
.put("enabled", true)
|
||||
.put("require_client_auth", "optional")
|
||||
.put("optional", false)
|
||||
.build());
|
||||
}).start())
|
||||
{
|
||||
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
|
||||
|
||||
// ssl connections should succeed
|
||||
com.datastax.driver.core.Cluster sslDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(false))
|
||||
.build();
|
||||
assertNotNull(sslDriver.connect());
|
||||
|
||||
// mtls connections should succeed
|
||||
com.datastax.driver.core.Cluster mtlsDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(true))
|
||||
.build();
|
||||
assertNotNull(mtlsDriver.connect());
|
||||
|
||||
// non-ssl connections should not succeed
|
||||
com.datastax.driver.core.Cluster nonSSLDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.build();
|
||||
expectedException.expect(NoHostAvailableException.class);
|
||||
assertNull(nonSSLDriver.connect());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalSSLMode() throws Exception
|
||||
{
|
||||
// When server is configured in optional ssl mode, it should accept
|
||||
// non-ssl, ssl
|
||||
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
|
||||
// Server configuration for optional mTLS mode
|
||||
c.with(Feature.NATIVE_PROTOCOL);
|
||||
c.set("client_encryption_options",
|
||||
ImmutableMap.builder().putAll(validKeystore)
|
||||
.put("enabled", true)
|
||||
.put("require_client_auth", "false")
|
||||
.put("optional", true)
|
||||
.build());
|
||||
}).start())
|
||||
{
|
||||
InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
|
||||
|
||||
// non-ssl connections should succeed
|
||||
com.datastax.driver.core.Cluster nonSSLDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.build();
|
||||
assertNotNull(nonSSLDriver.connect());
|
||||
|
||||
// ssl connections should succeed
|
||||
com.datastax.driver.core.Cluster sslDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(false))
|
||||
.build();
|
||||
assertNotNull(sslDriver.connect());
|
||||
|
||||
// mtls connections should succeed
|
||||
com.datastax.driver.core.Cluster mtlsDriver = com.datastax.driver.core.Cluster.builder()
|
||||
.addContactPoint(address.getHostAddress())
|
||||
.withSSL(sslOptions(true))
|
||||
.build();
|
||||
assertNotNull(mtlsDriver.connect());
|
||||
}
|
||||
}
|
||||
|
||||
private SSLOptions sslOptions(boolean withKeyStore) throws Exception
|
||||
{
|
||||
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
|
||||
sslContextBuilder.trustManager(createTrustManagerFactory("test/conf/cassandra_ssl_test.truststore", "cassandra"));
|
||||
if (withKeyStore)
|
||||
{
|
||||
sslContextBuilder.keyManager(createKeyManagerFactory("test/conf/cassandra_ssl_test_outbound.keystore", "cassandra"));
|
||||
}
|
||||
|
||||
SslContext sslContext = sslContextBuilder.build();
|
||||
return socketChannel -> sslContext.newHandler(socketChannel.alloc());
|
||||
}
|
||||
|
||||
private void testEndpointVerification(boolean requireEndpointVerification, boolean ipInSAN) throws Throwable
|
||||
{
|
||||
try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.junit.runners.Parameterized;
|
|||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
import org.apache.cassandra.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
|
|
@ -47,6 +48,7 @@ import org.apache.cassandra.utils.MBeanWrapper;
|
|||
import static org.apache.cassandra.auth.AuthTestUtils.getMockInetAddress;
|
||||
import static org.apache.cassandra.auth.AuthTestUtils.initializeIdentityRolesTable;
|
||||
import static org.apache.cassandra.auth.AuthTestUtils.loadCertificateChain;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
|
@ -78,7 +80,7 @@ public class MutualTlsAuthenticatorTest
|
|||
((CassandraRoleManager)DatabaseDescriptor.getRoleManager()).loadIdentityStatement();
|
||||
final Config config = DatabaseDescriptor.getRawConfig();
|
||||
config.client_encryption_options = config.client_encryption_options.withEnabled(true)
|
||||
.withRequireClientAuth(true);
|
||||
.withRequireClientAuth(REQUIRED);
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -172,6 +174,22 @@ public class MutualTlsAuthenticatorTest
|
|||
assertEquals("readonly_user", urnCache.get(identity2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateConfiguration()
|
||||
{
|
||||
// In strict mTLS mode mtls authenticator should check for require_client_auth to be true
|
||||
final Config config = DatabaseDescriptor.getRawConfig();
|
||||
String msg = "MutualTlsAuthenticator requires client_encryption_options.enabled to be true" +
|
||||
" & client_encryption_options.require_client_auth to be true";
|
||||
MutualTlsAuthenticator mutualTlsAuthenticator = createAndInitializeMtlsAuthenticator();
|
||||
|
||||
config.client_encryption_options = config.client_encryption_options.withEnabled(true)
|
||||
.withRequireClientAuth(EncryptionOptions.ClientAuth.NOT_REQUIRED);
|
||||
expectedException.expect(ConfigurationException.class);
|
||||
expectedException.expectMessage(msg);
|
||||
mutualTlsAuthenticator.validateConfiguration();
|
||||
}
|
||||
|
||||
MutualTlsAuthenticator createAndInitializeMtlsAuthenticator()
|
||||
{
|
||||
Map<String, String> parameters = Collections.singletonMap("validator_class_name", getValidatorClass());
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.junit.Test;
|
|||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
|
||||
import static org.apache.cassandra.auth.AuthTestUtils.getMockInetAddress;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
|
@ -50,7 +51,7 @@ public class MutualTlsWithPasswordFallbackAuthenticatorTest
|
|||
SchemaLoader.loadSchema();
|
||||
Config config = DatabaseDescriptor.getRawConfig();
|
||||
config.client_encryption_options = config.client_encryption_options.withEnabled(true)
|
||||
.withRequireClientAuth(true);
|
||||
.withRequireClientAuth(EncryptionOptions.ClientAuth.OPTIONAL);
|
||||
Map<String, String> parameters = Collections.singletonMap("validator_class_name", "org.apache.cassandra.auth.SpiffeCertificateValidator");
|
||||
fallbackAuthenticator = new MutualTlsWithPasswordFallbackAuthenticator(parameters);
|
||||
fallbackAuthenticator.setup();
|
||||
|
|
@ -88,4 +89,11 @@ public class MutualTlsWithPasswordFallbackAuthenticatorTest
|
|||
IAuthenticator.SaslNegotiator mutualtlsAuthenticator = fallbackAuthenticator.newSaslNegotiator(getMockInetAddress(), clientCertificatesCorp);
|
||||
assertTrue(mutualtlsAuthenticator instanceof MutualTlsAuthenticator.CertificateNegotiator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateConfiguration()
|
||||
{
|
||||
// In optional mTLS mode fallback authenticator should not check for require_client_auth to be true
|
||||
fallbackAuthenticator.validateConfiguration();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ public class ConfigCompatibilityTest
|
|||
.add("authenticator types do not match; org.apache.cassandra.config.ParameterizedClass != java.lang.String")
|
||||
.add("Property internode_authenticator used to be a value-type, but now is nested type class org.apache.cassandra.config.ParameterizedClass")
|
||||
.add("Property authenticator used to be a value-type, but now is nested type class org.apache.cassandra.config.ParameterizedClass")
|
||||
.add("require_client_auth types do not match; java.lang.String != java.lang.Boolean")
|
||||
.build();
|
||||
|
||||
/**
|
||||
|
|
@ -146,7 +147,7 @@ public class ConfigCompatibilityTest
|
|||
public void diff_5_0() throws IOException
|
||||
{
|
||||
diff(TEST_DIR + "/version=5.0-alpha1.yml", ImmutableSet.<String>builder()
|
||||
.build(), ImmutableSet.of());
|
||||
.build(), EXPECTED_FOR_50);
|
||||
}
|
||||
|
||||
private void diff(String original, Set<String> ignore, Set<String> expectedErrors) throws IOException
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import org.junit.Test;
|
|||
import org.apache.cassandra.security.DefaultSslContextFactory;
|
||||
import org.apache.cassandra.security.DummySslContextFactoryImpl;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ public class EncryptionOptionsEqualityTest
|
|||
.withOutboundKeystore("test/conf/cassandra_outbound.keystore")
|
||||
.withOutboundKeystorePassword("cassandra")
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +62,7 @@ public class EncryptionOptionsEqualityTest
|
|||
.withTrustStore("test/conf/cassandra_ssl_test.truststore")
|
||||
.withTrustStorePassword("cassandra")
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
EncryptionOptions encryptionOptions2 =
|
||||
|
|
@ -71,7 +73,7 @@ public class EncryptionOptionsEqualityTest
|
|||
.withTrustStore("test/conf/cassandra_ssl_test.truststore")
|
||||
.withTrustStorePassword("cassandra")
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
assertEquals(encryptionOptions1, encryptionOptions2);
|
||||
|
|
@ -88,7 +90,7 @@ public class EncryptionOptionsEqualityTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters1))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
Map<String,String> parameters2 = new HashMap<>();
|
||||
|
|
@ -98,7 +100,7 @@ public class EncryptionOptionsEqualityTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters2))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
assertEquals(encryptionOptions1, encryptionOptions2);
|
||||
|
|
@ -115,7 +117,7 @@ public class EncryptionOptionsEqualityTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters1))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withRequireEndpointVerification(true);
|
||||
|
||||
Map<String,String> parameters2 = new HashMap<>();
|
||||
|
|
@ -125,7 +127,7 @@ public class EncryptionOptionsEqualityTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DefaultSslContextFactory.class.getName(), parameters2))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withRequireEndpointVerification(true);
|
||||
|
||||
assertNotEquals(encryptionOptions1, encryptionOptions2);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class EncryptionOptionsTest
|
|||
new HashMap<>()),
|
||||
keystorePath, "dummypass",
|
||||
"dummytruststore", "dummypass",
|
||||
Collections.emptyList(), null, null, null, "JKS", false, false, enabled, optional)
|
||||
Collections.emptyList(), null, null, null, "JKS", "false", false, enabled, optional)
|
||||
.applyConfig(),
|
||||
expected,
|
||||
String.format("optional=%s keystore=%s enabled=%s", optional, keystorePath, enabled));
|
||||
|
|
@ -75,7 +75,7 @@ public class EncryptionOptionsTest
|
|||
customSslContextFactoryParams),
|
||||
keystorePath, "dummypass",
|
||||
"dummytruststore", "dummypass",
|
||||
Collections.emptyList(), null, null, null, "JKS", false, false, enabled, optional)
|
||||
Collections.emptyList(), null, null, null, "JKS", "false", false, enabled, optional)
|
||||
.applyConfig(),
|
||||
expected,
|
||||
String.format("optional=%s keystore=%s enabled=%s", optional, keystorePath, enabled));
|
||||
|
|
@ -126,7 +126,7 @@ public class EncryptionOptionsTest
|
|||
{
|
||||
return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory",
|
||||
new HashMap<>()), keystorePath, "dummypass", keystorePath, "dummypass", "dummytruststore", "dummypass",
|
||||
Collections.emptyList(), null, null, null, "JKS", false, false, optional, internodeEncryption, false)
|
||||
Collections.emptyList(), null, null, null, "JKS", "false", false, optional, internodeEncryption, false)
|
||||
.applyConfig(),
|
||||
expected,
|
||||
String.format("optional=%s keystore=%s internode=%s", optional, keystorePath, internodeEncryption));
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import org.apache.cassandra.cql3.CQLTester;
|
|||
import org.apache.cassandra.security.SSLFactory;
|
||||
import org.yaml.snakeyaml.introspector.Property;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
public class SettingsTableTest extends CQLTester
|
||||
{
|
||||
private static final String KS_NAME = "vts";
|
||||
|
|
@ -201,7 +203,7 @@ public class SettingsTableTest extends CQLTester
|
|||
|
||||
// name doesn't match yaml
|
||||
check(pre + "client_auth", "false");
|
||||
config.server_encryption_options = config.server_encryption_options.withRequireClientAuth(true);
|
||||
config.server_encryption_options = config.server_encryption_options.withRequireClientAuth(REQUIRED);
|
||||
check(pre + "client_auth", "true");
|
||||
|
||||
// name doesn't match yaml
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ import org.apache.cassandra.utils.FBUtilities;
|
|||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.net.MessagingService.VERSION_40;
|
||||
import static org.apache.cassandra.net.NoPayload.noPayload;
|
||||
import static org.apache.cassandra.net.MessagingService.current_version;
|
||||
|
|
@ -182,7 +183,7 @@ public class ConnectionTest
|
|||
.withKeyStorePassword("cassandra")
|
||||
.withTrustStore("test/conf/cassandra_ssl_test.truststore")
|
||||
.withTrustStorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA");
|
||||
|
||||
static final List<Function<Settings, Settings>> MODIFIERS = ImmutableList.of(
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import org.apache.cassandra.security.DefaultSslContextFactory;
|
|||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
|
||||
import static org.apache.cassandra.net.ConnectionType.SMALL_MESSAGES;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.net.MessagingService.current_version;
|
||||
import static org.apache.cassandra.net.MessagingService.minimum_version;
|
||||
import static org.apache.cassandra.net.OutboundConnectionInitiator.Result;
|
||||
|
|
@ -285,12 +287,12 @@ public class HandshakeTest
|
|||
if (sslConnectionType == SslFallbackConnectionType.MTLS)
|
||||
{
|
||||
serverEncryptionOptions = serverEncryptionOptions.withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all)
|
||||
.withRequireClientAuth(true);
|
||||
.withRequireClientAuth(REQUIRED);
|
||||
}
|
||||
else if (sslConnectionType == SslFallbackConnectionType.SSL)
|
||||
{
|
||||
serverEncryptionOptions = serverEncryptionOptions.withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all)
|
||||
.withRequireClientAuth(false);
|
||||
.withRequireClientAuth(NOT_REQUIRED);
|
||||
}
|
||||
return serverEncryptionOptions;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ import org.apache.cassandra.distributed.shared.WithProperties;
|
|||
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_TCACTIVE_OPENSSL;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
public class DefaultSslContextFactoryTest
|
||||
{
|
||||
private Map<String,Object> commonConfig = new HashMap<>();
|
||||
|
|
@ -46,7 +49,7 @@ public class DefaultSslContextFactoryTest
|
|||
{
|
||||
commonConfig.put("truststore", "test/conf/cassandra_ssl_test.truststore");
|
||||
commonConfig.put("truststore_password", "cassandra");
|
||||
commonConfig.put("require_client_auth", Boolean.FALSE);
|
||||
commonConfig.put("require_client_auth", "false");
|
||||
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
|
||||
}
|
||||
|
||||
|
|
@ -71,9 +74,9 @@ public class DefaultSslContextFactoryTest
|
|||
.withKeyStorePassword("cassandra")
|
||||
.withOutboundKeystore("test/conf/cassandra_ssl_test_outbound.keystore")
|
||||
.withOutboundKeystorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA");
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
Assert.assertNotNull(sslContext);
|
||||
if (OpenSsl.isAvailable())
|
||||
Assert.assertTrue(sslContext instanceof OpenSslContext);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import javax.net.ssl.SSLException;
|
|||
|
||||
import io.netty.handler.ssl.CipherSuiteFilter;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import org.apache.cassandra.config.EncryptionOptions;
|
||||
|
||||
/**
|
||||
* TEST ONLY Class. DON'T use it for anything else.
|
||||
|
|
@ -43,7 +44,19 @@ public class DummySslContextFactoryImpl implements ISslContextFactory
|
|||
}
|
||||
|
||||
@Override
|
||||
public SslContext createNettySslContext(boolean verifyPeerCertificate, SocketType socketType,
|
||||
public SSLContext createJSSESslContext(EncryptionOptions.ClientAuth clientAuth) throws SSLException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslContext createNettySslContext(boolean verifyPeerCertificate, SocketType socketType, CipherSuiteFilter cipherFilter) throws SSLException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslContext createNettySslContext(EncryptionOptions.ClientAuth clientAuth, SocketType socketType,
|
||||
CipherSuiteFilter cipherFilter) throws SSLException
|
||||
{
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import org.apache.cassandra.distributed.shared.WithProperties;
|
|||
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
|
||||
public class FileBasedSslContextFactoryTest
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactoryTest.class);
|
||||
|
|
@ -65,7 +67,7 @@ public class FileBasedSslContextFactoryTest
|
|||
new HashMap<>()))
|
||||
.withTrustStore("test/conf/cassandra_ssl_test.truststore")
|
||||
.withTrustStorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA")
|
||||
.withKeyStore("test/conf/cassandra_ssl_test.keystore")
|
||||
.withKeyStorePassword("cassandra")
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import org.apache.cassandra.config.ParameterizedClass;
|
|||
import org.apache.cassandra.distributed.shared.WithProperties;
|
||||
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_TCACTIVE_OPENSSL;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.ENCODED_CERTIFICATES;
|
||||
import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.ENCODED_KEY;
|
||||
import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.KEY_PASSWORD;
|
||||
|
|
@ -176,7 +178,7 @@ public class PEMBasedSslContextFactoryTest
|
|||
public void setup()
|
||||
{
|
||||
commonConfig.put(ENCODED_CERTIFICATES.getKeyName(), trusted_certificates);
|
||||
commonConfig.put("require_client_auth", Boolean.FALSE);
|
||||
commonConfig.put("require_client_auth", "false");
|
||||
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
|
||||
}
|
||||
|
||||
|
|
@ -215,10 +217,10 @@ public class PEMBasedSslContextFactoryTest
|
|||
EncryptionOptions options = new EncryptionOptions().withTrustStore("test/conf/cassandra_ssl_test.truststore.pem")
|
||||
.withKeyStore("test/conf/cassandra_ssl_test.keystore.pem")
|
||||
.withKeyStorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA")
|
||||
.withSslContextFactory(sslContextFactory);
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.SERVER, "test");
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.SERVER, "test");
|
||||
Assert.assertNotNull(sslContext);
|
||||
if (OpenSsl.isAvailable())
|
||||
Assert.assertTrue(sslContext instanceof OpenSslContext);
|
||||
|
|
@ -236,10 +238,10 @@ public class PEMBasedSslContextFactoryTest
|
|||
.withKeyStorePassword("cassandra")
|
||||
.withOutboundKeystore("test/conf/cassandra_ssl_test.keystore.pem")
|
||||
.withOutboundKeystorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA")
|
||||
.withSslContextFactory(sslContextFactory);
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext sslContext = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
Assert.assertNotNull(sslContext);
|
||||
if (OpenSsl.isAvailable())
|
||||
Assert.assertTrue(sslContext instanceof OpenSslContext);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions;
|
|||
import org.apache.cassandra.config.ParameterizedClass;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.NOT_REQUIRED;
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
|
|
@ -81,7 +83,7 @@ public class SSLFactoryTest
|
|||
encryptionOptions = new ServerEncryptionOptions()
|
||||
.withTrustStore("test/conf/cassandra_ssl_test.truststore")
|
||||
.withTrustStorePassword("cassandra")
|
||||
.withRequireClientAuth(false)
|
||||
.withRequireClientAuth(NOT_REQUIRED)
|
||||
.withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA")
|
||||
.withSslContextFactory(new ParameterizedClass(TestFileBasedSSLContextFactory.class.getName(),
|
||||
new HashMap<>()));
|
||||
|
|
@ -118,8 +120,8 @@ public class SSLFactoryTest
|
|||
options.sslContextFactoryInstance.initHotReloading();
|
||||
legacyOptions.sslContextFactoryInstance.initHotReloading();
|
||||
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
File keystoreFile = new File(options.keystore);
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
|
|
@ -127,8 +129,8 @@ public class SSLFactoryTest
|
|||
keystoreFile.trySetLastModified(System.currentTimeMillis() + 15000);
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
|
||||
Assert.assertNotSame(oldCtx, newCtx);
|
||||
Assert.assertNotSame(oldLegacyCtx, newLegacyCtx);
|
||||
|
|
@ -151,7 +153,7 @@ public class SSLFactoryTest
|
|||
.withOutboundKeystorePassword("dummyPassword");
|
||||
|
||||
// Server socket type should create a keystore with keystore & keystore password
|
||||
final OpenSslServerContext context = (OpenSslServerContext) SSLFactory.createNettySslContext(options, true, ISslContextFactory.SocketType.SERVER);
|
||||
final OpenSslServerContext context = (OpenSslServerContext) SSLFactory.createNettySslContext(options, REQUIRED, ISslContextFactory.SocketType.SERVER);
|
||||
assertNotNull(context);
|
||||
|
||||
// Verify if right certificate is loaded into SslContext
|
||||
|
|
@ -168,7 +170,7 @@ public class SSLFactoryTest
|
|||
.withKeyStorePassword("dummyPassword");
|
||||
|
||||
// Client socket type should create a keystore with outbound Keystore & outbound password
|
||||
final OpenSslClientContext context = (OpenSslClientContext) SSLFactory.createNettySslContext(options, true, ISslContextFactory.SocketType.CLIENT);
|
||||
final OpenSslClientContext context = (OpenSslClientContext) SSLFactory.createNettySslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT);
|
||||
assertNotNull(context);
|
||||
|
||||
// Verify if right certificate is loaded into SslContext
|
||||
|
|
@ -189,8 +191,8 @@ public class SSLFactoryTest
|
|||
options.sslContextFactoryInstance.initHotReloading();
|
||||
legacyOptions.sslContextFactoryInstance.initHotReloading();
|
||||
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
File keystoreFile = new File(options.keystore);
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
|
|
@ -198,8 +200,8 @@ public class SSLFactoryTest
|
|||
keystoreFile.trySetLastModified(System.currentTimeMillis() + 15000);
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
|
||||
Assert.assertNotSame(oldCtx, newCtx);
|
||||
Assert.assertNotSame(oldLegacyCtx, newLegacyCtx);
|
||||
|
|
@ -221,7 +223,7 @@ public class SSLFactoryTest
|
|||
.withKeyStorePassword("bad password")
|
||||
.withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all);
|
||||
|
||||
SSLFactory.validateSslContext("testSslFactorySslInit_BadPassword_ThrowsException", options, false, true);
|
||||
SSLFactory.validateSslContext("testSslFactorySslInit_BadPassword_ThrowsException", options, NOT_REQUIRED, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -239,14 +241,14 @@ public class SSLFactoryTest
|
|||
|
||||
SSLFactory.initHotReloading(options, options, true); // deliberately not initializing with legacyOptions to match InboundSockets.addBindings
|
||||
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
|
||||
changeKeystorePassword(options.keystore, options.keystore_password, "bad password");
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test legacy");
|
||||
|
||||
Assert.assertSame(oldCtx, newCtx);
|
||||
Assert.assertSame(oldLegacyCtx, newLegacyCtx);
|
||||
|
|
@ -270,14 +272,14 @@ public class SSLFactoryTest
|
|||
|
||||
|
||||
SSLFactory.initHotReloading(options, options, true);
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
|
||||
testKeystoreFile.trySetLastModified(System.currentTimeMillis() + 15000);
|
||||
FileUtils.forceDelete(testKeystoreFile.toJavaIOFile());
|
||||
|
||||
SSLFactory.checkCertFilesForHotReloading();
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, REQUIRED, ISslContextFactory.SocketType.CLIENT, "test");
|
||||
|
||||
Assert.assertSame(oldCtx, newCtx);
|
||||
}
|
||||
|
|
@ -298,7 +300,7 @@ public class SSLFactoryTest
|
|||
ServerEncryptionOptions options = addKeystoreOptions(encryptionOptions)
|
||||
.withCipherSuites("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
|
||||
|
||||
SslContext ctx1 = SSLFactory.getOrCreateSslContext(options, true,
|
||||
SslContext ctx1 = SSLFactory.getOrCreateSslContext(options, REQUIRED,
|
||||
ISslContextFactory.SocketType.SERVER, "test");
|
||||
|
||||
Assert.assertTrue(ctx1.isServer());
|
||||
|
|
@ -306,7 +308,7 @@ public class SSLFactoryTest
|
|||
|
||||
options = options.withCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
|
||||
|
||||
SslContext ctx2 = SSLFactory.getOrCreateSslContext(options, true,
|
||||
SslContext ctx2 = SSLFactory.getOrCreateSslContext(options, REQUIRED,
|
||||
ISslContextFactory.SocketType.CLIENT, "test");
|
||||
|
||||
Assert.assertTrue(ctx2.isClient());
|
||||
|
|
@ -323,7 +325,7 @@ public class SSLFactoryTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters1))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
SSLFactory.CacheKey cacheKey1 = new SSLFactory.CacheKey(encryptionOptions1, ISslContextFactory.SocketType.SERVER, "test"
|
||||
|
|
@ -336,7 +338,7 @@ public class SSLFactoryTest
|
|||
new EncryptionOptions()
|
||||
.withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters2))
|
||||
.withProtocol("TLSv1.1")
|
||||
.withRequireClientAuth(true)
|
||||
.withRequireClientAuth(REQUIRED)
|
||||
.withRequireEndpointVerification(false);
|
||||
|
||||
SSLFactory.CacheKey cacheKey2 = new SSLFactory.CacheKey(encryptionOptions2, ISslContextFactory.SocketType.SERVER, "test"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ import org.apache.cassandra.config.EncryptionOptions;
|
|||
import org.apache.cassandra.security.SSLFactory;
|
||||
import org.apache.cassandra.stress.settings.StressSettings;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientAuth.REQUIRED;
|
||||
|
||||
public class JavaDriverClient
|
||||
{
|
||||
|
||||
|
|
@ -161,7 +163,7 @@ public class JavaDriverClient
|
|||
if (encryptionOptions.getEnabled())
|
||||
{
|
||||
SSLContext sslContext;
|
||||
sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
|
||||
sslContext = SSLFactory.createSSLContext(encryptionOptions, REQUIRED);
|
||||
|
||||
// Temporarily override newSSLEngine to set accepted protocols until it is added to
|
||||
// RemoteEndpointAwareJdkSSLOptions. See CASSANDRA-13325 and CASSANDRA-16362.
|
||||
|
|
|
|||
Loading…
Reference in New Issue