diff --git a/CHANGES.txt b/CHANGES.txt index bf626abca2..635fa71f4e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,7 @@ * Fix SSTableloader issue when restoring a table named backups (CASSANDRA-16235) * Invalid serialized size for responses caused by increasing message time by 1ms which caused extra bytes in size calculation (CASSANDRA-16103) * Throw BufferOverflowException from DataOutputBuffer for better visibility (CASSANDRA-16214) + * TLS connections to the storage port on a node without server encryption configured causes java.io.IOException accessing missing keystore (CASSANDRA-16144) Merged from 3.11: Merged from 3.0: * Prevent invoking enable/disable gossip when not in NORMAL (CASSANDRA-16146) diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 0387105fb4..5fbb220cad 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -760,11 +760,16 @@ public class DatabaseDescriptor throw new ConfigurationException("commitlog_segment_size_in_mb must be at least twice the size of max_mutation_size_in_kb / 1024", false); // native transport encryption options - if (conf.native_transport_port_ssl != null - && conf.native_transport_port_ssl != conf.native_transport_port - && !conf.client_encryption_options.isEnabled()) + if (conf.client_encryption_options != null) { - throw new ConfigurationException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl", false); + conf.client_encryption_options.applyConfig(); + + if (conf.native_transport_port_ssl != null + && conf.native_transport_port_ssl != conf.native_transport_port + && conf.client_encryption_options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) + { + throw new ConfigurationException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl", false); + } } if (conf.max_value_size_in_mb <= 0) @@ -789,6 +794,16 @@ public class DatabaseDescriptor if (conf.otc_coalescing_enough_coalesced_messages <= 0) throw new ConfigurationException("otc_coalescing_enough_coalesced_messages must be positive", false); + if (conf.server_encryption_options != null) + { + conf.server_encryption_options.applyConfig(); + + if (conf.server_encryption_options.enable_legacy_ssl_storage_port && + conf.server_encryption_options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) + { + throw new ConfigurationException("enable_legacy_ssl_storage_port is true (enabled) with internode encryption disabled (none). Enable encryption or disable the legacy ssl storage port."); + } + } Integer maxMessageSize = conf.internode_max_message_size_in_bytes; if (maxMessageSize != null) { diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java index 8ccf6d2ba1..26bf4580f0 100644 --- a/src/java/org/apache/cassandra/config/EncryptionOptions.java +++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java @@ -17,16 +17,39 @@ */ package org.apache.cassandra.config; +import java.io.File; import java.util.List; import java.util.Objects; import com.google.common.collect.ImmutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.cassandra.locator.IEndpointSnitch; import org.apache.cassandra.locator.InetAddressAndPort; public class EncryptionOptions { + Logger logger = LoggerFactory.getLogger(EncryptionOptions.class); + + public enum TlsEncryptionPolicy + { + UNENCRYPTED("unencrypted"), OPTIONAL("optionally encrypted"), ENCRYPTED("encrypted"); + + private final String description; + + TlsEncryptionPolicy(String description) + { + this.description = description; + } + + public String description() + { + return description; + } + } + public final String keystore; public final String keystore_password; public final String truststore; @@ -43,8 +66,12 @@ public class EncryptionOptions // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate // classes so we can choose appropriate configuration for each. // See CASSANDRA-15262 and CASSANDRA-15146 - private boolean enabled; - public final Boolean optional; + protected Boolean enabled; + protected Boolean optional; + + // Calculated by calling applyConfig() after populating/parsing + protected Boolean isEnabled = null; + protected Boolean isOptional = null; public EncryptionOptions() { @@ -58,11 +85,11 @@ public class EncryptionOptions store_type = "JKS"; require_client_auth = false; require_endpoint_verification = false; - enabled = false; - optional = true; + enabled = null; + optional = null; } - public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, boolean enabled, Boolean optional) + public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled, Boolean optional) { this.keystore = keystore; this.keystore_password = keystore_password; @@ -75,14 +102,7 @@ public class EncryptionOptions this.require_client_auth = require_client_auth; this.require_endpoint_verification = require_endpoint_verification; this.enabled = enabled; - if (optional != null) { - this.optional = optional; - } else { - // If someone is asking for an _insecure_ connection and not explicitly telling us to refuse - // encrypted connections we assume they would like to be able to transition to encrypted connections - // in the future. - this.optional = !enabled; - } + this.optional = optional; } public EncryptionOptions(EncryptionOptions options) @@ -98,14 +118,48 @@ public class EncryptionOptions require_client_auth = options.require_client_auth; require_endpoint_verification = options.require_endpoint_verification; enabled = options.enabled; - if (options.optional != null) { - optional = options.optional; - } else { - // If someone is asking for an _insecure_ connection and not explicitly telling us to refuse - // encrypted connections we assume they would like to be able to transition to encrypted connections - // in the future. - optional = !enabled; + this.optional = options.optional; + } + + /* Computes enabled and optional before use. Because the configuration can be loaded + * through pluggable mechanisms this is the only safe way to make sure that + * enabled and optional are set correctly. + */ + public EncryptionOptions applyConfig() + { + ensureConfigNotApplied(); + + isEnabled = this.enabled != null && enabled; + + if (optional != null) + { + isOptional = optional; } + // If someone is asking for an _insecure_ connection and not explicitly telling us to refuse + // encrypted connections AND they have a keystore file, we assume they would like to be able + // to transition to encrypted connections in the future. + else if (new File(keystore).exists()) + { + isOptional = !isEnabled; + } + else + { + // Otherwise if there's no keystore, not possible to establish an optional secure connection + isOptional = false; + } + return this; + } + + private void ensureConfigApplied() + { + if (isEnabled == null || isOptional == null) + throw new IllegalStateException("EncryptionOptions.applyConfig must be called first"); + } + + private void ensureConfigNotApplied() + { + if (isEnabled != null || isOptional != null) + throw new IllegalStateException("EncryptionOptions cannot be changed after configuration applied"); } /** @@ -113,109 +167,153 @@ public class EncryptionOptions * * @return if the channel should be encrypted */ - public boolean isEnabled() { - return this.enabled; + public Boolean isEnabled() { + ensureConfigApplied(); + return isEnabled; } /** * Sets if encryption should be enabled for this channel. Note that this should only be called by * the configuration parser or tests. It is public only for that purpose, mutating enabled state * is probably a bad idea. - * @param enabled + * @param enabled value to set */ - public void setEnabled(boolean enabled) { + public void setEnabled(Boolean enabled) { + ensureConfigNotApplied(); this.enabled = enabled; } + /** + * Indicates if the channel may be encrypted (but is not required to be). + * Explicitly providing a value in the configuration take precedent. + * If no optional value is set and !isEnabled(), then optional connections are allowed + * if a keystore exists. Without it, it would be impossible to establish the connections. + * + * Return type is Boolean even though it can never be null so that snakeyaml can find it + * @return if the channel may be encrypted + */ + public Boolean isOptional() + { + ensureConfigApplied(); + return isOptional; + } + + /** + * Sets if encryption should be optional for this channel. Note that this should only be called by + * the configuration parser or tests. It is public only for that purpose, mutating enabled state + * is probably a bad idea. + * @param optional value to set + */ + public void setOptional(boolean optional) { + ensureConfigNotApplied(); + this.optional = optional; + } + + public TlsEncryptionPolicy tlsEncryptionPolicy() + { + if (isOptional()) + { + return TlsEncryptionPolicy.OPTIONAL; + } + else if (isEnabled()) + { + return TlsEncryptionPolicy.ENCRYPTED; + } + else + { + return TlsEncryptionPolicy.UNENCRYPTED; + } + } + public EncryptionOptions withKeyStore(String keystore) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withKeyStorePassword(String keystore_password) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withTrustStore(String truststore) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withTrustStorePassword(String truststore_password) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withCipherSuites(List cipher_suites) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withCipherSuites(String ... cipher_suites) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites), protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withProtocol(String protocol) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withAlgorithm(String algorithm) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withStoreType(String store_type) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withRequireClientAuth(boolean require_client_auth) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } public EncryptionOptions withEnabled(boolean enabled) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } - public EncryptionOptions withOptional(boolean optional) + public EncryptionOptions withOptional(Boolean optional) { return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - enabled, optional); + enabled, optional).applyConfig(); } /** @@ -260,8 +358,8 @@ public class EncryptionOptions result += 31 * (protocol == null ? 0 : protocol.hashCode()); result += 31 * (algorithm == null ? 0 : algorithm.hashCode()); result += 31 * (store_type == null ? 0 : store_type.hashCode()); - result += 31 * Boolean.hashCode(enabled); - result += 31 * Boolean.hashCode(optional); + 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 * Boolean.hashCode(require_endpoint_verification); @@ -286,7 +384,7 @@ public class EncryptionOptions public ServerEncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean optional, InternodeEncryption internode_encryption, boolean enable_legacy_ssl_storage_port) { - super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, internode_encryption != InternodeEncryption.none, optional); + super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, null, optional); this.internode_encryption = internode_encryption; this.enable_legacy_ssl_storage_port = enable_legacy_ssl_storage_port; } @@ -298,8 +396,30 @@ public class EncryptionOptions this.enable_legacy_ssl_storage_port = options.enable_legacy_ssl_storage_port; } - public boolean isEnabled() { - return this.internode_encryption != InternodeEncryption.none; + @Override + public EncryptionOptions applyConfig() + { + return applyConfigInternal(); + } + + private ServerEncryptionOptions applyConfigInternal() + { + + + super.applyConfig(); + + isEnabled = this.internode_encryption != InternodeEncryption.none; + + if (this.enabled != null && this.enabled && !isEnabled) + { + logger.warn("Setting server_encryption_options.enabled has no effect, use internode_encryption"); + } + + // regardless of the optional flag, if the internode encryption is set to rack or dc + // it must be optional so that unencrypted connections within the rack or dc can be established. + isOptional = super.isOptional || internode_encryption == InternodeEncryption.rack || internode_encryption == InternodeEncryption.dc; + + return this; } public boolean shouldEncrypt(InetAddressAndPort endpoint) @@ -330,98 +450,98 @@ public class EncryptionOptions { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withKeyStorePassword(String keystore_password) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withTrustStore(String truststore) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withTrustStorePassword(String truststore_password) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withCipherSuites(List cipher_suites) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withCipherSuites(String ... cipher_suites) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites), protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withProtocol(String protocol) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withAlgorithm(String algorithm) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withStoreType(String store_type) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withRequireClientAuth(boolean require_client_auth) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withOptional(boolean optional) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withInternodeEncryption(InternodeEncryption internode_encryption) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } public ServerEncryptionOptions withLegacySslStoragePort(boolean enable_legacy_ssl_storage_port) { return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, - optional, internode_encryption, enable_legacy_ssl_storage_port); + optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal(); } } diff --git a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java index 511a90356b..f661611b51 100644 --- a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java +++ b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java @@ -153,7 +153,9 @@ public class YamlConfigurationLoader implements ConfigurationLoader return node; } }); - return (T) constructor.getSingleData(klass); + T value = (T) constructor.getSingleData(klass); + propertiesChecker.check(); + return value; } static class CustomConstructor extends CustomClassLoaderConstructor diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java index d11f69a904..e47ce8ca87 100644 --- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java +++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java @@ -166,7 +166,7 @@ final class SettingsTable extends AbstractVirtualTable result.row(f.getName() + "_cipher_suites").column(VALUE, value.cipher_suites.toString()); result.row(f.getName() + "_client_auth").column(VALUE, Boolean.toString(value.require_client_auth)); result.row(f.getName() + "_endpoint_verification").column(VALUE, Boolean.toString(value.require_endpoint_verification)); - result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.optional)); + result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.isOptional())); if (value instanceof EncryptionOptions.ServerEncryptionOptions) { diff --git a/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java b/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java index f2339ebd24..d21358a9dd 100644 --- a/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java +++ b/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java @@ -24,8 +24,6 @@ import java.util.List; import java.util.concurrent.Future; import java.util.function.Consumer; -import javax.net.ssl.SSLSession; - import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +59,6 @@ import static org.apache.cassandra.net.MessagingService.VERSION_40; import static org.apache.cassandra.net.MessagingService.current_version; import static org.apache.cassandra.net.MessagingService.minimum_version; import static org.apache.cassandra.net.SocketFactory.WIRETRACE; -import static org.apache.cassandra.net.SocketFactory.encryptionLogStatement; import static org.apache.cassandra.net.SocketFactory.newSslHandler; public class InboundConnectionInitiator @@ -98,17 +95,19 @@ public class InboundConnectionInitiator // order of handlers: ssl -> logger -> handshakeHandler // For either unencrypted or transitional modes, allow Ssl optionally. - if (settings.encryption.optional) + switch(settings.encryption.tlsEncryptionPolicy()) { - pipeline.addFirst("ssl", new OptionalSslHandler(settings.encryption)); - } - else - { - SslContext sslContext = SSLFactory.getOrCreateSslContext(settings.encryption, true, SSLFactory.SocketType.SERVER); - InetSocketAddress peer = settings.encryption.require_endpoint_verification ? channel.remoteAddress() : null; - SslHandler sslHandler = newSslHandler(channel, sslContext, peer); - logger.trace("creating inbound netty SslContext: context={}, engine={}", sslContext.getClass().getName(), sslHandler.engine().getClass().getName()); - pipeline.addFirst("ssl", sslHandler); + case UNENCRYPTED: + // Handler checks for SSL connection attempts and cleanly rejects them if encryption is disabled + pipeline.addFirst("rejectssl", new RejectSslHandler()); + break; + case OPTIONAL: + pipeline.addFirst("ssl", new OptionalSslHandler(settings.encryption)); + break; + case ENCRYPTED: + SslHandler sslHandler = getSslHandler("creating", channel, settings.encryption); + pipeline.addFirst("ssl", sslHandler); + break; } if (WIRETRACE) @@ -214,7 +213,6 @@ public class InboundConnectionInitiator failHandshake(ctx); }, HandshakeProtocol.TIMEOUT_MILLIS, MILLISECONDS); - logSsl(ctx); authenticate(ctx.channel().remoteAddress()); } @@ -231,17 +229,6 @@ public class InboundConnectionInitiator throw new IOException("Authentication failure for inbound connection from peer " + addr); } - private void logSsl(ChannelHandlerContext ctx) - { - SslHandler sslHandler = ctx.pipeline().get(SslHandler.class); - if (sslHandler != null) - { - SSLSession session = sslHandler.engine().getSession(); - logger.info("connection from peer {} to {}, protocol = {}", - ctx.channel().remoteAddress(), ctx.channel().localAddress(), session.getProtocol()); - } - } - @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { @@ -401,7 +388,7 @@ public class InboundConnectionInitiator channel.id().asShortText()), current_version, initiate.framing, - pipeline.get("ssl") != null ? encryptionLogStatement(pipeline.channel(), settings.encryption) : "disabled"); + SocketFactory.encryptionConnectionSummary(pipeline.channel())); } @VisibleForTesting @@ -460,7 +447,7 @@ public class InboundConnectionInitiator handler.id(true), useMessagingVersion, initiate.framing, - pipeline.get("ssl") != null ? encryptionLogStatement(pipeline.channel(), settings.encryption) : "disabled"); + SocketFactory.encryptionConnectionSummary(pipeline.channel())); pipeline.addLast("deserialize", handler); @@ -468,6 +455,16 @@ public class InboundConnectionInitiator } } + private static SslHandler getSslHandler(String description, Channel channel, EncryptionOptions.ServerEncryptionOptions encryptionOptions) throws IOException + { + final boolean buildTrustStore = true; + SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, buildTrustStore, SSLFactory.SocketType.SERVER); + InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null; + SslHandler sslHandler = newSslHandler(channel, sslContext, peer); + logger.trace("{} inbound netty SslContext: context={}, engine={}", description, sslContext.getClass().getName(), sslHandler.engine().getClass().getName()); + return sslHandler; + } + private static class OptionalSslHandler extends ByteToMessageDecoder { private final EncryptionOptions.ServerEncryptionOptions encryptionOptions; @@ -489,10 +486,7 @@ public class InboundConnectionInitiator if (SslHandler.isEncrypted(in)) { // Connection uses SSL/TLS, replace the detection handler with a SslHandler and so use encryption. - SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true, SSLFactory.SocketType.SERVER); - Channel channel = ctx.channel(); - InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null; - SslHandler sslHandler = newSslHandler(channel, sslContext, peer); + SslHandler sslHandler = getSslHandler("replacing optional", ctx.channel(), encryptionOptions); ctx.pipeline().replace(this, "ssl", sslHandler); } else @@ -503,4 +497,31 @@ public class InboundConnectionInitiator } } } + + private static class RejectSslHandler extends ByteToMessageDecoder + { + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) + { + if (in.readableBytes() < 5) + { + // To detect if SSL must be used we need to have at least 5 bytes, so return here and try again + // once more bytes a ready. + return; + } + + if (SslHandler.isEncrypted(in)) + { + logger.info("Rejected incoming TLS connection before negotiating from {} to {}. TLS is explicitly disabled by configuration.", + ctx.channel().remoteAddress(), ctx.channel().localAddress()); + in.readBytes(in.readableBytes()); // discard the readable bytes so not called again + ctx.close(); + } + else + { + // Incoming connection did not attempt TLS/SSL encryption, just remove the detection handler and continue without + // SslHandler in the pipeline. + ctx.pipeline().remove(this); + } + } + } } diff --git a/src/java/org/apache/cassandra/net/InboundConnectionSettings.java b/src/java/org/apache/cassandra/net/InboundConnectionSettings.java index 20f185aa7f..00def4fc47 100644 --- a/src/java/org/apache/cassandra/net/InboundConnectionSettings.java +++ b/src/java/org/apache/cassandra/net/InboundConnectionSettings.java @@ -84,7 +84,7 @@ public class InboundConnectionSettings public String toString() { return format("address: (%s), nic: %s, encryption: %s", - bindAddress, FBUtilities.getNetworkInterface(bindAddress.address), SocketFactory.encryptionLogStatement(null, encryption)); + bindAddress, FBUtilities.getNetworkInterface(bindAddress.address), SocketFactory.encryptionOptionsSummary(encryption)); } public InboundConnectionSettings withAuthenticator(IInternodeAuthenticator authenticator) @@ -152,12 +152,12 @@ public class InboundConnectionSettings acceptMessaging, acceptStreaming, socketFactory, handlers); } - public InboundConnectionSettings withLegacyDefaults() + public InboundConnectionSettings withLegacySslStoragePortDefaults() { ServerEncryptionOptions encryption = this.encryption; if (encryption == null) encryption = DatabaseDescriptor.getInternodeMessagingEncyptionOptions(); - encryption = encryption.withOptional(false); + encryption = encryption.withOptional(false).withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); return this.withBindAddress(bindAddress.withPort(DatabaseDescriptor.getSSLStoragePort())) .withEncryption(encryption) diff --git a/src/java/org/apache/cassandra/net/InboundSockets.java b/src/java/org/apache/cassandra/net/InboundSockets.java index 93caf85163..fc5722417c 100644 --- a/src/java/org/apache/cassandra/net/InboundSockets.java +++ b/src/java/org/apache/cassandra/net/InboundSockets.java @@ -200,7 +200,7 @@ class InboundSockets private static void addBindings(InboundConnectionSettings template, ImmutableList.Builder out) { InboundConnectionSettings settings = template.withDefaults(); - InboundConnectionSettings legacySettings = template.withLegacyDefaults(); + InboundConnectionSettings legacySettings = template.withLegacySslStoragePortDefaults(); if (settings.encryption.enable_legacy_ssl_storage_port) { @@ -266,4 +266,4 @@ class InboundSockets { return sockets; } -} \ No newline at end of file +} diff --git a/src/java/org/apache/cassandra/net/OutboundConnection.java b/src/java/org/apache/cassandra/net/OutboundConnection.java index 66f14dbb2b..79c045933b 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnection.java +++ b/src/java/org/apache/cassandra/net/OutboundConnection.java @@ -30,7 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.stream.Stream; import javax.annotation.Nullable; @@ -1152,7 +1151,7 @@ public class OutboundConnection id(true), success.messagingVersion, settings.framing, - encryptionLogStatement(channel, settings.encryption)); + encryptionConnectionSummary(channel)); break; case RETRY: diff --git a/src/java/org/apache/cassandra/net/OutboundConnectionSettings.java b/src/java/org/apache/cassandra/net/OutboundConnectionSettings.java index 5f83b6a989..1aab412594 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnectionSettings.java +++ b/src/java/org/apache/cassandra/net/OutboundConnectionSettings.java @@ -35,7 +35,6 @@ import org.apache.cassandra.utils.FBUtilities; import static org.apache.cassandra.config.DatabaseDescriptor.getEndpointSnitch; import static org.apache.cassandra.net.MessagingService.VERSION_40; import static org.apache.cassandra.net.MessagingService.instance; -import static org.apache.cassandra.net.SocketFactory.encryptionLogStatement; import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort; /** @@ -171,7 +170,7 @@ public class OutboundConnectionSettings public String toString() { return String.format("peer: (%s, %s), framing: %s, encryption: %s", - to, connectTo, framing, encryptionLogStatement(encryption)); + to, connectTo, framing, SocketFactory.encryptionOptionsSummary(encryption)); } public OutboundConnectionSettings withAuthenticator(IInternodeAuthenticator authenticator) diff --git a/src/java/org/apache/cassandra/net/SocketFactory.java b/src/java/org/apache/cassandra/net/SocketFactory.java index a8ee72990a..8300c2a052 100644 --- a/src/java/org/apache/cassandra/net/SocketFactory.java +++ b/src/java/org/apache/cassandra/net/SocketFactory.java @@ -228,39 +228,41 @@ public final class SocketFactory return sslHandler; } - static String encryptionLogStatement(EncryptionOptions options) + /** + * Summarizes the intended encryption options, suitable for logging. Once a connection is established, use + * {@link SocketFactory#encryptionConnectionSummary} below. + * @param options options to summarize + * @return description of encryption options + */ + static String encryptionOptionsSummary(EncryptionOptions options) { - if (options == null) - return "disabled"; + if (options == null || options.tlsEncryptionPolicy() == EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) + return EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED.description(); String encryptionType = SSLFactory.openSslIsAvailable() ? "openssl" : "jdk"; - return "enabled (" + encryptionType + ')'; + return options.tlsEncryptionPolicy().description() + '(' + encryptionType + ')'; } - static String encryptionLogStatement(Channel channel, EncryptionOptions options) + /** + * Summarizes the encryption status of a channel, suitable for logging. + * @return description of channel encryption + */ + static String encryptionConnectionSummary(Channel channel) { - if (options == null || !options.isEnabled()) - return "disabled"; - - StringBuilder sb = new StringBuilder(64); - if (options.optional) - sb.append("optional (factory="); - else - sb.append("enabled (factory="); - sb.append(SSLFactory.openSslIsAvailable() ? "openssl" : "jdk"); - - final SslHandler sslHandler = channel == null ? null : channel.pipeline().get(SslHandler.class); - if (sslHandler != null) + final SslHandler sslHandler = channel.pipeline().get(SslHandler.class); + if (sslHandler == null) { - SSLSession session = sslHandler.engine().getSession(); - sb.append(";protocol=") - .append(session.getProtocol()) - .append(";cipher=") - .append(session.getCipherSuite()); + return EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED.description(); } + SSLSession session = sslHandler.engine().getSession(); - sb.append(')'); - return sb.toString(); + return "encrypted(factory=" + + (SSLFactory.openSslIsAvailable() ? "openssl" : "jdk") + + ";protocol=" + + (session != null ? session.getProtocol() : "MISSING SESSION") + + ";cipher=" + + (session != null ? session.getCipherSuite() : "MISSING SESSION") + + ')'; } EventLoopGroup defaultGroup() diff --git a/src/java/org/apache/cassandra/security/SSLFactory.java b/src/java/org/apache/cassandra/security/SSLFactory.java index e51ce461db..94db564b52 100644 --- a/src/java/org/apache/cassandra/security/SSLFactory.java +++ b/src/java/org/apache/cassandra/security/SSLFactory.java @@ -229,7 +229,7 @@ public final class SSLFactory } catch (Exception e) { - throw new IOException("failed to build trust manager store for secure connections", e); + throw new IOException("failed to build key manager store for secure connections", e); } } @@ -370,13 +370,13 @@ public final class SSLFactory List fileList = new ArrayList<>(); - if (serverOpts != null && serverOpts.isEnabled()) + if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) { fileList.add(new HotReloadableFile(serverOpts.keystore)); fileList.add(new HotReloadableFile(serverOpts.truststore)); } - if (clientOpts != null && clientOpts.isEnabled()) + if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) { fileList.add(new HotReloadableFile(clientOpts.keystore)); fileList.add(new HotReloadableFile(clientOpts.truststore)); @@ -405,8 +405,8 @@ public final class SSLFactory { try { - // Ensure we're able to create both server & client SslContexts - if (serverOpts != null && serverOpts.isEnabled()) + // Ensure we're able to create both server & client SslContexts if they might ever be needed + if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) { createNettySslContext(serverOpts, true, SocketType.SERVER, openSslIsAvailable()); createNettySslContext(serverOpts, true, SocketType.CLIENT, openSslIsAvailable()); @@ -419,8 +419,8 @@ public final class SSLFactory try { - // Ensure we're able to create both server & client SslContexts - if (clientOpts != null && clientOpts.isEnabled()) + // Ensure we're able to create both server & client SslContexts if they might ever be needed + if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED) { createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.SERVER, openSslIsAvailable()); createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.CLIENT, openSslIsAvailable()); diff --git a/src/java/org/apache/cassandra/service/NativeTransportService.java b/src/java/org/apache/cassandra/service/NativeTransportService.java index 6cdce3f452..f4f35856e3 100644 --- a/src/java/org/apache/cassandra/service/NativeTransportService.java +++ b/src/java/org/apache/cassandra/service/NativeTransportService.java @@ -32,7 +32,9 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.Version; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.Server; @@ -79,27 +81,40 @@ public class NativeTransportService .withEventLoopGroup(workerGroup) .withHost(nativeAddr); - if (!DatabaseDescriptor.getNativeProtocolEncryptionOptions().isEnabled()) + EncryptionOptions.TlsEncryptionPolicy encryptionPolicy = DatabaseDescriptor.getNativeProtocolEncryptionOptions().tlsEncryptionPolicy(); + Server regularPortServer; + Server tlsPortServer = null; + + // If an SSL port is separately supplied for the native transport, listen for unencrypted connections on the + // regular port, and encryption / optionally encrypted connections on the ssl port. + if (nativePort != nativePortSSL) { - servers = Collections.singleton(builder.withSSL(false).withPort(nativePort).build()); + regularPortServer = builder.withTlsEncryptionPolicy(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED).withPort(nativePort).build(); + switch(encryptionPolicy) + { + case OPTIONAL: // FALLTHRU - encryption is optional on the regular port, but encrypted on the tls port. + case ENCRYPTED: + tlsPortServer = builder.withTlsEncryptionPolicy(encryptionPolicy).withPort(nativePortSSL).build(); + break; + case UNENCRYPTED: // Should have been caught by DatabaseDescriptor.applySimpleConfig + throw new IllegalStateException("Encryption must be enabled in client_encryption_options for native_transport_port_ssl"); + default: + throw new IllegalStateException("Unrecognized TLS encryption policy: " + encryptionPolicy); + } + } + // Otherwise, if only the regular port is supplied, listen as the encryption policy specifies + else + { + regularPortServer = builder.withTlsEncryptionPolicy(encryptionPolicy).withPort(nativePort).build(); + } + + if (tlsPortServer == null) + { + servers = Collections.singleton(regularPortServer); } else { - if (nativePort != nativePortSSL) - { - // user asked for dedicated ssl port for supporting both non-ssl and ssl connections - servers = Collections.unmodifiableList( - Arrays.asList( - builder.withSSL(false).withPort(nativePort).build(), - builder.withSSL(true).withPort(nativePortSSL).build() - ) - ); - } - else - { - // ssl only mode using configured native port - servers = Collections.singleton(builder.withSSL(true).withPort(nativePort).build()); - } + servers = Collections.unmodifiableList(Arrays.asList(regularPortServer, tlsPortServer)); } ClientMetrics.instance.init(servers); @@ -112,6 +127,7 @@ public class NativeTransportService */ public void start() { + logger.info("Using Netty Version: {}", Version.identify().entrySet()); initialize(); servers.forEach(Server::start); } diff --git a/src/java/org/apache/cassandra/tools/LoaderOptions.java b/src/java/org/apache/cassandra/tools/LoaderOptions.java index 686c834122..070ffa88da 100644 --- a/src/java/org/apache/cassandra/tools/LoaderOptions.java +++ b/src/java/org/apache/cassandra/tools/LoaderOptions.java @@ -439,8 +439,10 @@ public class LoaderOptions else sslStoragePort = config.ssl_storage_port; throttle = config.stream_throughput_outbound_megabits_per_sec; - clientEncOptions = config.client_encryption_options; + // Copy the encryption options and apply the config so that argument parsing can accesss isEnabled. + clientEncOptions = config.client_encryption_options.applyConfig(); serverEncOptions = config.server_encryption_options; + serverEncOptions.applyConfig(); if (cmd.hasOption(THROTTLE_MBITS)) { @@ -473,11 +475,13 @@ public class LoaderOptions if (cmd.hasOption(SSL_TRUSTSTORE)) { clientEncOptions = clientEncOptions.withTrustStore(cmd.getOptionValue(SSL_TRUSTSTORE)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_TRUSTSTORE_PW)) { clientEncOptions = clientEncOptions.withTrustStorePassword(cmd.getOptionValue(SSL_TRUSTSTORE_PW)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_KEYSTORE)) @@ -485,31 +489,37 @@ public class LoaderOptions // if a keystore was provided, lets assume we'll need to use clientEncOptions = clientEncOptions.withKeyStore(cmd.getOptionValue(SSL_KEYSTORE)) .withRequireClientAuth(true); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_KEYSTORE_PW)) { clientEncOptions = clientEncOptions.withKeyStorePassword(cmd.getOptionValue(SSL_KEYSTORE_PW)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_PROTOCOL)) { clientEncOptions = clientEncOptions.withProtocol(cmd.getOptionValue(SSL_PROTOCOL)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_ALGORITHM)) { clientEncOptions = clientEncOptions.withAlgorithm(cmd.getOptionValue(SSL_ALGORITHM)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_STORE_TYPE)) { clientEncOptions = clientEncOptions.withStoreType(cmd.getOptionValue(SSL_STORE_TYPE)); + clientEncOptions.applyConfig(); } if (cmd.hasOption(SSL_CIPHER_SUITES)) { clientEncOptions = clientEncOptions.withCipherSuites(cmd.getOptionValue(SSL_CIPHER_SUITES).split(",")); + clientEncOptions.applyConfig(); } if (cmd.hasOption(TARGET_KEYSPACE)) diff --git a/src/java/org/apache/cassandra/transport/Client.java b/src/java/org/apache/cassandra/transport/Client.java index ec86579f59..4f87cf41b6 100644 --- a/src/java/org/apache/cassandra/transport/Client.java +++ b/src/java/org/apache/cassandra/transport/Client.java @@ -50,7 +50,7 @@ public class Client extends SimpleClient public Client(String host, int port, ProtocolVersion version, EncryptionOptions encryptionOptions) { - super(host, port, version, version.isBeta(), encryptionOptions); + super(host, port, version, version.isBeta(), new EncryptionOptions(encryptionOptions).applyConfig()); setEventHandler(eventHandler); } @@ -294,7 +294,7 @@ public class Client extends SimpleClient int port = Integer.parseInt(args[1]); ProtocolVersion version = args.length == 3 ? ProtocolVersion.decode(Integer.parseInt(args[2]), DatabaseDescriptor.getNativeTransportAllowOlderProtocols()) : ProtocolVersion.CURRENT; - EncryptionOptions encryptionOptions = new EncryptionOptions(); + EncryptionOptions encryptionOptions = new EncryptionOptions().applyConfig(); System.out.println("CQL binary protocol console " + host + "@" + port + " using native protocol version " + version); new Client(host, port, version, encryptionOptions).run(); diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index b1fad53023..52e6387db2 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -48,7 +48,6 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; -import io.netty.util.Version; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -88,7 +87,7 @@ public class Server implements CassandraDaemon.Server }; public final InetSocketAddress socket; - public boolean useSSL = false; + public final EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy; private final AtomicBoolean isRunning = new AtomicBoolean(false); private EventLoopGroup workerGroup; @@ -96,7 +95,7 @@ public class Server implements CassandraDaemon.Server private Server (Builder builder) { this.socket = builder.getSocket(); - this.useSSL = builder.useSSL; + this.tlsEncryptionPolicy = builder.tlsEncryptionPolicy; if (builder.workerGroup != null) { workerGroup = builder.workerGroup; @@ -141,29 +140,27 @@ public class Server implements CassandraDaemon.Server if (workerGroup != null) bootstrap = bootstrap.group(workerGroup); - if (this.useSSL) - { - final EncryptionOptions clientEnc = DatabaseDescriptor.getNativeProtocolEncryptionOptions(); + final EncryptionOptions clientEnc = DatabaseDescriptor.getNativeProtocolEncryptionOptions(); - if (clientEnc.optional) - { - logger.info("Enabling optionally encrypted CQL connections between client and server"); - bootstrap.childHandler(new OptionalSecureInitializer(this, clientEnc)); - } - else - { - logger.info("Enabling encrypted CQL connections between client and server"); - bootstrap.childHandler(new SecureInitializer(this, clientEnc)); - } - } - else + switch (this.tlsEncryptionPolicy) { - bootstrap.childHandler(new Initializer(this)); + case UNENCRYPTED: + bootstrap.childHandler(new Initializer(this)); + break; + case OPTIONAL: + logger.debug("Enabling optionally encrypted CQL connections between client and server"); + bootstrap.childHandler(new OptionalSecureInitializer(this, clientEnc)); + break; + case ENCRYPTED: + logger.debug("Enabling encrypted CQL connections between client and server"); + bootstrap.childHandler(new SecureInitializer(this, clientEnc)); + break; + default: + throw new IllegalStateException("Unrecognized TLS encryption policy: " + this.tlsEncryptionPolicy); } // Bind and start to accept incoming connections. - logger.info("Using Netty Version: {}", Version.identify().entrySet()); - logger.info("Starting listening for CQL clients on {} ({})...", socket, this.useSSL ? "encrypted" : "unencrypted"); + logger.info("Starting listening for CQL clients on {} ({})...", socket, clientEnc.tlsEncryptionPolicy().description()); ChannelFuture bindFuture = bootstrap.bind(socket); if (!bindFuture.awaitUninterruptibly().isSuccess()) @@ -219,14 +216,14 @@ public class Server implements CassandraDaemon.Server { private EventLoopGroup workerGroup; private EventExecutor eventExecutorGroup; - private boolean useSSL = false; + private EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy = EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED; private InetAddress hostAddr; private int port = -1; private InetSocketAddress socket; - public Builder withSSL(boolean useSSL) + public Builder withTlsEncryptionPolicy(EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy) { - this.useSSL = useSSL; + this.tlsEncryptionPolicy = tlsEncryptionPolicy; return this; } diff --git a/src/java/org/apache/cassandra/transport/SimpleClient.java b/src/java/org/apache/cassandra/transport/SimpleClient.java index ef3c1dbba2..b4e93736bb 100644 --- a/src/java/org/apache/cassandra/transport/SimpleClient.java +++ b/src/java/org/apache/cassandra/transport/SimpleClient.java @@ -114,7 +114,7 @@ public class SimpleClient implements Closeable throw new IllegalArgumentException(String.format("Beta version of server used (%s), but USE_BETA flag is not set", version)); this.version = version; - this.encryptionOptions = encryptionOptions; + this.encryptionOptions = new EncryptionOptions(encryptionOptions).applyConfig(); } public SimpleClient(String host, int port) diff --git a/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java b/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java index e243451b1d..bf615cd508 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java @@ -57,6 +57,7 @@ public class InstanceConfig implements IInstanceConfig public final UUID hostId; public UUID hostId() { return hostId; } private final Map params = new TreeMap<>(); + private final Map dtestParams = new TreeMap<>(); private final EnumSet featureFlags; @@ -125,6 +126,7 @@ public class InstanceConfig implements IInstanceConfig this.num = copy.num; this.networkTopology = new NetworkTopology(copy.networkTopology); this.params.putAll(copy.params); + this.dtestParams.putAll(copy.dtestParams); this.hostId = copy.hostId; this.featureFlags = copy.featureFlags; this.broadcastAddressAndPort = copy.broadcastAddressAndPort; @@ -190,8 +192,7 @@ public class InstanceConfig implements IInstanceConfig { if (value == null) value = NULL; - - params.put(fieldName, value); + getParams(fieldName).put(fieldName, value); return this; } @@ -201,10 +202,18 @@ public class InstanceConfig implements IInstanceConfig value = NULL; // test value - params.put(fieldName, value); + getParams(fieldName).put(fieldName, value); return this; } + private Map getParams(String fieldName) + { + Map map = params; + if (fieldName.startsWith("dtest")) + map = dtestParams; + return map; + } + public void propagate(Object writeToConfig, Map, Function> mapping) { throw new IllegalStateException("In-JVM dtests no longer support propagate"); @@ -218,17 +227,17 @@ public class InstanceConfig implements IInstanceConfig public Object get(String name) { - return params.get(name); + return getParams(name).get(name); } public int getInt(String name) { - return (Integer)params.get(name); + return (Integer) get(name); } public String getString(String name) { - return (String)params.get(name); + return (String) get(name); } public Map getParams() diff --git a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java new file mode 100644 index 0000000000..6c482993bc --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java @@ -0,0 +1,295 @@ +/* + * 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 java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import com.google.common.collect.ImmutableMap; +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.FutureListener; +import org.apache.cassandra.config.EncryptionOptions; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.security.SSLFactory; +import org.apache.cassandra.utils.concurrent.SimpleCondition; + +public class AbstractEncryptionOptionsImpl extends TestBaseImpl +{ + Logger logger = LoggerFactory.getLogger(EncryptionOptions.class); + final static String validKeyStorePath = "test/conf/cassandra_ssl_test.keystore"; + final static String validKeyStorePassword = "cassandra"; + final static String validTrustStorePath = "test/conf/cassandra_ssl_test.truststore"; + final static String validTrustStorePassword = "cassandra"; + final static Map validKeystore = ImmutableMap.of("keystore", validKeyStorePath, + "keystore_password", validKeyStorePassword, + "truststore", validTrustStorePath, + "truststore_password", validTrustStorePassword); + + // Result of a TlsConnection.connect call. The result is updated as the TLS connection + // sequence takes place. The nextOnFailure/nextOnSuccess allows the discard handler + // to correctly update state if an unexpected exception is thrown. + public enum ConnectResult { + UNINITIALIZED, + FAILED_TO_NEGOTIATE, + NEVER_CONNECTED, + NEGOTIATED, + CONNECTED_AND_ABOUT_TO_NEGOTIATE(FAILED_TO_NEGOTIATE, NEGOTIATED), + CONNECTING(NEVER_CONNECTED, CONNECTED_AND_ABOUT_TO_NEGOTIATE); + + public final ConnectResult nextOnFailure; + public final ConnectResult nextOnSuccess; + + ConnectResult() + { + nextOnFailure = null; + nextOnSuccess = null; + } + ConnectResult(ConnectResult nextOnFailure, ConnectResult nextOnSuccess) + { + this.nextOnFailure = nextOnFailure; + this.nextOnSuccess = nextOnSuccess; + } + } + + public class TlsConnection + { + final String host; + final int port; + final EncryptionOptions encryptionOptions = new EncryptionOptions() + .withEnabled(true) + .withKeyStore(validKeyStorePath).withKeyStorePassword(validKeyStorePassword) + .withTrustStore(validTrustStorePath).withTrustStorePassword(validTrustStorePassword); + private Throwable lastThrowable; + + public TlsConnection(String host, int port) + { + this.host = host; + this.port = port; + } + + public synchronized Throwable lastThrowable() + { + return lastThrowable; + } + private synchronized void setLastThrowable(Throwable cause) + { + lastThrowable = cause; + } + + final AtomicReference result = new AtomicReference<>(ConnectResult.UNINITIALIZED); + + void setResult(String why, ConnectResult expected, ConnectResult newResult) + { + if (newResult == null) + return; + logger.debug("Setting progress from {} to {}", expected, expected.nextOnSuccess); + result.getAndUpdate(v -> { + if (v == expected) + return newResult; + else + throw new IllegalStateException( + String.format("CAS attempt on %s failed from %s to %s but %s did not match expected value", + why, expected, newResult, v)); + }); + } + void successProgress() + { + ConnectResult current = result.get(); + setResult("success", current, current.nextOnSuccess); + } + void failure() + { + ConnectResult current = result.get(); + setResult("failure", current, current.nextOnFailure); + } + + ConnectResult connect() throws Throwable + { + AtomicInteger connectAttempts = new AtomicInteger(0); + result.set(ConnectResult.UNINITIALIZED); + setLastThrowable(null); + + SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true, + SSLFactory.SocketType.CLIENT); + + EventLoopGroup workerGroup = new NioEventLoopGroup(); + Bootstrap b = new Bootstrap(); + SimpleCondition attemptCompleted = new SimpleCondition(); + + // Listener on the SSL handshake makes sure that the test completes immediately as + // the server waits to receive a message over the TLS connection, so the discardHandler.decode + // will likely never be called. The lambda has to handle it's own exceptions as it's a listener, + // not in the request pipeline to pass them on to discardHandler. + FutureListener handshakeResult = channelFuture -> { + try + { + logger.debug("handshakeFuture() listener called"); + channelFuture.get(); + successProgress(); + } + catch (Throwable cause) + { + logger.info("handshakeFuture() threw", cause); + failure(); + setLastThrowable(cause); + } + attemptCompleted.signalAll(); + }; + + ChannelHandler connectHandler = new ByteToMessageDecoder() + { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception + { + logger.debug("connectHandler.channelActive"); + int count = connectAttempts.incrementAndGet(); + if (count > 1) + { + logger.info("connectHandler.channelActive called more than once - {}", count); + } + successProgress(); + + // Add the handler after the connection is established to make sure the connection + // progress is recorded + final SslHandler sslHandler = ctx.pipeline().get(SslHandler.class); + sslHandler.handshakeFuture().addListener(handshakeResult); + + super.channelActive(ctx); + } + + @Override + public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) + { + logger.debug("connectHandler.decode - readable bytes {}", in.readableBytes()); + + ctx.pipeline().remove(this); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + { + logger.debug("connectHandler.exceptionCaught", cause); + setLastThrowable(cause); + failure(); + attemptCompleted.signalAll(); + } + }; + ChannelHandler discardHandler = new ByteToMessageDecoder() + { + @Override + public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) + { + logger.info("discardHandler.decode - {} readable bytes made it past SSL negotiation, discarding.", + in.readableBytes()); + in.readBytes(in.readableBytes()); + attemptCompleted.signalAll(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + { + logger.debug("discardHandler.exceptionCaught", cause); + setLastThrowable(cause); + failure(); + attemptCompleted.signalAll(); + } + }; + + b.group(workerGroup); + b.channel(NioSocketChannel.class); + b.handler(new ChannelInitializer() + { + @Override + protected void initChannel(Channel channel) + { + SslHandler sslHandler = sslContext.newHandler(channel.alloc()); + channel.pipeline().addFirst(connectHandler, sslHandler, discardHandler); + } + }); + + result.set(ConnectResult.CONNECTING); + ChannelFuture f = b.connect(host, port); + try + { + f.sync(); + attemptCompleted.await(15, TimeUnit.SECONDS); + } + finally + { + f.channel().close(); + } + return result.get(); + } + + void assertCannotConnect() throws Throwable + { + try + { + connect(); + } + catch (java.net.ConnectException ex) + { + // verify it was not possible to connect before starting the server + } + } + } + + /* Provde the cluster cannot start with the configured options */ + void assertCannotStartDueToConfigurationException(Cluster cluster) + { + Throwable tr = null; + try + { + cluster.startup(); + } + catch (Throwable maybeConfigException) + { + tr = maybeConfigException; + } + + if (tr == null) + { + Assert.fail("Expected a ConfigurationException"); + } + else + { + Assert.assertEquals(ConfigurationException.class.getName(), tr.getClass().getName()); + } + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/IncRepairTruncationTest.java b/test/distributed/org/apache/cassandra/distributed/test/IncRepairTruncationTest.java index bd47906883..4ffdaa9f19 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/IncRepairTruncationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/IncRepairTruncationTest.java @@ -49,8 +49,7 @@ public class IncRepairTruncationTest extends TestBaseImpl { ExecutorService es = Executors.newFixedThreadPool(3); try(Cluster cluster = init(Cluster.build(2) - .withConfig(config -> config.set("disable_incremental_repair", false) - .with(GOSSIP) + .withConfig(config -> config.with(GOSSIP) .with(NETWORK)) .start())) { diff --git a/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java new file mode 100644 index 0000000000..4b05ade029 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java @@ -0,0 +1,218 @@ +/* + * 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 java.net.InetAddress; + +import com.google.common.collect.ImmutableMap; +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.Feature; + +public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImpl +{ + @Test + public void nodeWillNotStartWithBadKeystoreTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + c.set("server_encryption_options", + ImmutableMap.of("optional", true, + "keystore", "/path/to/bad/keystore/that/should/not/exist", + "truststore", "/path/to/bad/truststore/that/should/not/exist")); + }).createWithoutStarting()) + { + assertCannotStartDueToConfigurationException(cluster); + } + } + + @Test + public void legacySslPortProvidedWithEncryptionNoneWillNotStartTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + c.set("ssl_storage_port", 7013); + c.set("server_encryption_options", + ImmutableMap.builder().putAll(validKeystore) + .put("internode_encryption", "none") + .put("optional", false) + .put("enable_legacy_ssl_storage_port", "true") + .build()); + }).createWithoutStarting()) + { + assertCannotStartDueToConfigurationException(cluster); + } + } + + @Test + public void optionalTlsConnectionDisabledWithoutKeystoreTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> c.with(Feature.NETWORK)).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int port = cluster.get(1).config().broadcastAddress().getPort(); + + TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port); + tlsConnection.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS connection should not be possible without keystore", + ConnectResult.FAILED_TO_NEGOTIATE, tlsConnection.connect()); + } + } + + @Test + public void optionalTlsConnectionAllowedWithKeystoreTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + c.set("server_encryption_options", validKeystore); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int port = cluster.get(1).config().broadcastAddress().getPort(); + + TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port); + tlsConnection.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS connection should be possible with keystore by default", + ConnectResult.NEGOTIATED, tlsConnection.connect()); + } + } + + @Test + public void optionalTlsConnectionAllowedToStoragePortTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + c.set("storage_port", 7012); + c.set("ssl_storage_port", 7013); + c.set("server_encryption_options", + ImmutableMap.builder().putAll(validKeystore) + .put("internode_encryption", "none") + .put("optional", true) + .put("enable_legacy_ssl_storage_port", "true") + .build()); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int regular_port = (int) cluster.get(1).config().get("storage_port"); + int ssl_port = (int) cluster.get(1).config().get("ssl_storage_port"); + + // Create the connections and prove they cannot connect before server start + TlsConnection connectToRegularPort = new TlsConnection(address.getHostAddress(), regular_port); + connectToRegularPort.assertCannotConnect(); + + TlsConnection connectToSslStoragePort = new TlsConnection(address.getHostAddress(), ssl_port); + connectToSslStoragePort.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS native connection should be possible to ssl_storage_port", + ConnectResult.NEGOTIATED, connectToSslStoragePort.connect()); + Assert.assertEquals("TLS native connection should be possible with valid keystore by default", + ConnectResult.NEGOTIATED, connectToRegularPort.connect()); + } + } + + @Test + public void legacySslStoragePortEnabledWithSameRegularAndSslPortTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + 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) + .put("internode_encryption", "none") + .put("optional", true) + .put("enable_legacy_ssl_storage_port", "true") + .build()); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int ssl_port = (int) cluster.get(1).config().get("ssl_storage_port"); + + // Create the connections and prove they cannot connect before server start + TlsConnection connectToSslStoragePort = new TlsConnection(address.getHostAddress(), ssl_port); + connectToSslStoragePort.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS native connection should be possible to ssl_storage_port", + ConnectResult.NEGOTIATED, connectToSslStoragePort.connect()); + } + } + + + @Test + public void tlsConnectionRejectedWhenUnencrypted() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NETWORK); + c.set("server_encryption_options", + ImmutableMap.builder().putAll(validKeystore) + .put("internode_encryption", "none") + .put("optional", false) + .build()); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int regular_port = (int) cluster.get(1).config().get("storage_port"); + + // Create the connections and prove they cannot connect before server start + TlsConnection connection = new TlsConnection(address.getHostAddress(), regular_port); + connection.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS native connection should be possible with valid keystore by default", + ConnectResult.FAILED_TO_NEGOTIATE, connection.connect()); + } + } + + @Test + public void allInternodeEncryptionEstablishedTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(2).withConfig(c -> { + c.with(Feature.NETWORK) + .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) + .put("internode_encryption", "all") + .build()); + }).start()) + { + // Just check startup - cluster should not be able to establish internode connections xwithout encrypted connections + for (int i = 1; i <= cluster.size(); i++) + { + Object[][] result = cluster.get(i).executeInternal("SELECT successful_connection_attempts, address, port FROM system_views.internode_outbound"); + Assert.assertEquals(1, result.length); + long successfulConnectionAttempts = (long) result[0][0]; + Assert.assertTrue("At least one connection: " + successfulConnectionAttempts, successfulConnectionAttempts > 0); + } + } + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java new file mode 100644 index 0000000000..d64b038923 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java @@ -0,0 +1,137 @@ +/* + * 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 java.net.InetAddress; + +import com.google.common.collect.ImmutableMap; +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.Feature; + +public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOptionsImpl +{ + @Test + public void nodeWillNotStartWithBadKeystore() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NATIVE_PROTOCOL); + c.set("client_encryption_options", + ImmutableMap.of("enabled", true, + "optional", true, + "keystore", "/path/to/bad/keystore/that/should/not/exist", + "truststore", "/path/to/bad/truststore/that/should/not/exist")); + }).createWithoutStarting()) + { + assertCannotStartDueToConfigurationException(cluster); + } + } + + @Test + public void optionalTlsConnectionDisabledWithoutKeystoreTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> c.with(Feature.NATIVE_PROTOCOL)).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int port = (int) cluster.get(1).config().get("native_transport_port"); + + TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port); + tlsConnection.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS connection should not be possible without keystore", + ConnectResult.FAILED_TO_NEGOTIATE, tlsConnection.connect()); + } + } + + + @Test + public void optionalTlsConnectionAllowedWithKeystoreTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NATIVE_PROTOCOL); + c.set("client_encryption_options", validKeystore); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int port = (int) cluster.get(1).config().get("native_transport_port"); + + TlsConnection tlsConnection = new TlsConnection(address.getHostAddress(), port); + tlsConnection.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS native connection should be possible with keystore by default", + ConnectResult.NEGOTIATED, tlsConnection.connect()); + } + } + + @Test + public void optionalTlsConnectionAllowedToRegularPortTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NATIVE_PROTOCOL); + c.set("native_transport_port_ssl", 9043); + c.set("client_encryption_options", + ImmutableMap.builder().putAll(validKeystore) + .put("enabled", false) + .put("optional", true) + .build()); + }).createWithoutStarting()) + { + InetAddress address = cluster.get(1).config().broadcastAddress().getAddress(); + int unencrypted_port = (int) cluster.get(1).config().get("native_transport_port"); + int ssl_port = (int) cluster.get(1).config().get("native_transport_port_ssl"); + + // Create the connections and prove they cannot connect before server start + TlsConnection connectionToUnencryptedPort = new TlsConnection(address.getHostAddress(), unencrypted_port); + connectionToUnencryptedPort.assertCannotConnect(); + + TlsConnection connectionToEncryptedPort = new TlsConnection(address.getHostAddress(), ssl_port); + connectionToEncryptedPort.assertCannotConnect(); + + cluster.startup(); + + Assert.assertEquals("TLS native connection should be possible to native_transport_port_ssl", + ConnectResult.NEGOTIATED, connectionToEncryptedPort.connect()); + Assert.assertEquals("TLS native connection should not be possible on the regular port if an SSL port is specified", + ConnectResult.FAILED_TO_NEGOTIATE, connectionToUnencryptedPort.connect()); // but did connect + } + } + + @Test + public void unencryptedNativeConnectionNotlisteningOnTlsPortTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(1).withConfig(c -> { + c.with(Feature.NATIVE_PROTOCOL); + c.set("native_transport_port_ssl", 9043); + c.set("client_encryption_options", + ImmutableMap.builder().putAll(validKeystore) + .put("enabled", false) + .put("optional", false) + .build()); + }).createWithoutStarting()) + { + assertCannotStartDueToConfigurationException(cluster); + } + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/PreviewRepairTest.java b/test/distributed/org/apache/cassandra/distributed/test/PreviewRepairTest.java index 87837f29f8..bc9eda7bcd 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/PreviewRepairTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/PreviewRepairTest.java @@ -179,8 +179,7 @@ public class PreviewRepairTest extends TestBaseImpl public void testConcurrentIncRepairDuringPreview() throws IOException, InterruptedException, ExecutionException { try (Cluster cluster = init(Cluster.build(2).withConfig(config -> - config.set("disable_incremental_repair", false) - .with(GOSSIP) + config.with(GOSSIP) .with(NETWORK)).start())) { cluster.schemaChange("create table " + KEYSPACE + ".tbl (id int primary key, t int)"); diff --git a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java new file mode 100644 index 0000000000..59638adb73 --- /dev/null +++ b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java @@ -0,0 +1,178 @@ +/* + * 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.config; + +import java.io.File; +import java.util.Collections; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.assertj.core.api.Assertions; + +import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED; +import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.OPTIONAL; +import static org.apache.cassandra.config.EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED; +import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.all; +import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.dc; +import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.none; +import static org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption.rack; +import static org.junit.Assert.*; + +public class EncryptionOptionsTest +{ + static class EncryptionOptionsTestCase + { + final EncryptionOptions encryptionOptions; + final EncryptionOptions.TlsEncryptionPolicy expected; + final String description; + + public EncryptionOptionsTestCase(EncryptionOptions encryptionOptions, EncryptionOptions.TlsEncryptionPolicy expected, String description) + { + this.encryptionOptions = encryptionOptions; + this.expected = expected; + this.description = description; + } + + public static EncryptionOptionsTestCase of(Boolean optional, String keystorePath, Boolean enabled, EncryptionOptions.TlsEncryptionPolicy expected) + { + return new EncryptionOptionsTestCase(new EncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass", + Collections.emptyList(), "TLS", null, "JKS", false, false, enabled, optional) + .applyConfig(), + expected, + String.format("optional=%s keystore=%s enabled=%s", optional, keystorePath, enabled)); + } + } + + static String absentKeystore = "test/conf/missing-keystore-is-not-here"; + static String presentKeystore = "test/conf/keystore.jks"; + EncryptionOptionsTestCase[] encryptionOptionTestCases = { + // Optional Keystore Enabled Expected + EncryptionOptionsTestCase.of(null, absentKeystore, false, UNENCRYPTED), + EncryptionOptionsTestCase.of(null, absentKeystore, true, ENCRYPTED), + EncryptionOptionsTestCase.of(null, presentKeystore, false, OPTIONAL), + EncryptionOptionsTestCase.of(null, presentKeystore, true, ENCRYPTED), + EncryptionOptionsTestCase.of(false, absentKeystore, false, UNENCRYPTED), + EncryptionOptionsTestCase.of(false, absentKeystore, true, ENCRYPTED), + EncryptionOptionsTestCase.of(true, presentKeystore, false, OPTIONAL), + EncryptionOptionsTestCase.of(true, presentKeystore, true, OPTIONAL) + }; + + @Test + public void testEncryptionOptionPolicy() + { + assertTrue(new File(presentKeystore).exists()); + assertFalse(new File(absentKeystore).exists()); + for (EncryptionOptionsTestCase testCase : encryptionOptionTestCases) + { + Assert.assertSame(testCase.description, testCase.expected, testCase.encryptionOptions.tlsEncryptionPolicy()); + } + } + + static class ServerEncryptionOptionsTestCase + { + final EncryptionOptions encryptionOptions; + final EncryptionOptions.TlsEncryptionPolicy expected; + final String description; + + public ServerEncryptionOptionsTestCase(EncryptionOptions encryptionOptions, EncryptionOptions.TlsEncryptionPolicy expected, String description) + { + this.encryptionOptions = encryptionOptions; + this.expected = expected; + this.description = description; + } + + public static ServerEncryptionOptionsTestCase of(Boolean optional, String keystorePath, + EncryptionOptions.ServerEncryptionOptions.InternodeEncryption internodeEncryption, + EncryptionOptions.TlsEncryptionPolicy expected) + { + return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass", + Collections.emptyList(), "TLS", null, "JKS", false, false, optional, internodeEncryption, false) + .applyConfig(), + expected, + String.format("optional=%s keystore=%s internode=%s", optional, keystorePath, internodeEncryption)); + } + } + + @Test + public void isEnabledServer() + { + Map yaml = ImmutableMap.of( + "server_encryption_options", ImmutableMap.of( + "isEnabled", false + ) + ); + + Assertions.assertThatThrownBy(() -> YamlConfigurationLoader.fromMap(yaml, Config.class)) + .isInstanceOf(ConfigurationException.class) + .hasMessage("Invalid yaml. Please remove properties [isEnabled] from your cassandra.yaml"); + } + + @Test + public void isOptionalServer() + { + Map yaml = ImmutableMap.of( + "server_encryption_options", ImmutableMap.of( + "isOptional", false + ) + ); + + Assertions.assertThatThrownBy(() -> YamlConfigurationLoader.fromMap(yaml, Config.class)) + .isInstanceOf(ConfigurationException.class) + .hasMessage("Invalid yaml. Please remove properties [isOptional] from your cassandra.yaml"); + } + + ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = { + + // Optional Keystore Internode Expected + ServerEncryptionOptionsTestCase.of(null, absentKeystore, none, UNENCRYPTED), + ServerEncryptionOptionsTestCase.of(null, absentKeystore, rack, OPTIONAL), + ServerEncryptionOptionsTestCase.of(null, absentKeystore, dc, OPTIONAL), + ServerEncryptionOptionsTestCase.of(null, absentKeystore, all, ENCRYPTED), + + ServerEncryptionOptionsTestCase.of(null, presentKeystore, none, OPTIONAL), + ServerEncryptionOptionsTestCase.of(null, presentKeystore, rack, OPTIONAL), + ServerEncryptionOptionsTestCase.of(null, absentKeystore, dc, OPTIONAL), + ServerEncryptionOptionsTestCase.of(null, absentKeystore, all, ENCRYPTED), + + ServerEncryptionOptionsTestCase.of(false, absentKeystore, none, UNENCRYPTED), + ServerEncryptionOptionsTestCase.of(false, absentKeystore, rack, OPTIONAL), + ServerEncryptionOptionsTestCase.of(false, absentKeystore, dc, OPTIONAL), + ServerEncryptionOptionsTestCase.of(false, absentKeystore, all, ENCRYPTED), + + ServerEncryptionOptionsTestCase.of(true, presentKeystore, none, OPTIONAL), + ServerEncryptionOptionsTestCase.of(true, presentKeystore, rack, OPTIONAL), + ServerEncryptionOptionsTestCase.of(true, absentKeystore, dc, OPTIONAL), + ServerEncryptionOptionsTestCase.of(true, absentKeystore, all, OPTIONAL), + }; + + @Test + public void testServerEncryptionOptionPolicy() + { + assertTrue(new File(presentKeystore).exists()); + assertFalse(new File(absentKeystore).exists()); + for (ServerEncryptionOptionsTestCase testCase : serverEncryptionOptionTestCases) + { + Assert.assertSame(testCase.description, testCase.expected, testCase.encryptionOptions.tlsEncryptionPolicy()); + } + } +} diff --git a/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java b/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java index f705217cca..2aff83f5b9 100644 --- a/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java +++ b/test/unit/org/apache/cassandra/config/YamlConfigurationLoaderTest.java @@ -35,18 +35,19 @@ public class YamlConfigurationLoaderTest int storagePort = 123; Config.CommitLogSync commitLogSync = Config.CommitLogSync.batch; ParameterizedClass seedProvider = new ParameterizedClass("org.apache.cassandra.locator.SimpleSeedProvider", Collections.emptyMap()); - EncryptionOptions encryptionOptions = new EncryptionOptions() - .withKeyStore("myNewKeystore") - .withCipherSuites("SomeCipher") - .withOptional(false); + Map encryptionOptions = ImmutableMap.of("cipher_suites", Collections.singletonList("FakeCipher"), + "optional", false, + "enabled", true); Map map = ImmutableMap.of("storage_port", storagePort, "commitlog_sync", commitLogSync, "seed_provider", seedProvider, "client_encryption_options", encryptionOptions); + Config config = YamlConfigurationLoader.fromMap(map, Config.class); assertEquals(storagePort, config.storage_port); // Check a simple integer assertEquals(commitLogSync, config.commitlog_sync); // Check an enum assertEquals(seedProvider, config.seed_provider); // Check a parameterized class - assertEquals(encryptionOptions, config.client_encryption_options); // Check a nested object + assertEquals(false, config.client_encryption_options.optional); // Check a nested object + assertEquals(true, config.client_encryption_options.enabled); // Check a nested object } } diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index d205c10e62..254c0429bd 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -1045,7 +1045,7 @@ public abstract class CQLTester protected SimpleClient newSimpleClient(ProtocolVersion version, boolean compression, boolean checksums, boolean isOverloadedException) throws IOException { - return new SimpleClient(nativeAddr.getHostAddress(), nativePort, version, version.isBeta(), new EncryptionOptions()).connect(compression, checksums, isOverloadedException); + return new SimpleClient(nativeAddr.getHostAddress(), nativePort, version, version.isBeta(), new EncryptionOptions().applyConfig()).connect(compression, checksums, isOverloadedException); } protected SimpleClient newSimpleClient(ProtocolVersion version, boolean compression, boolean checksums) throws IOException diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java index 7a27282b15..65297c7a09 100644 --- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java @@ -54,6 +54,8 @@ public class SettingsTableTest extends CQLTester public void config() { config = new Config(); + config.client_encryption_options.applyConfig(); + config.server_encryption_options.applyConfig(); table = new SettingsTable(KS_NAME, config); VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); } @@ -151,9 +153,9 @@ public class SettingsTableTest extends CQLTester config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv5"); check(pre + "protocol", "TLSv5"); - check(pre + "optional", "true"); - config.server_encryption_options = config.server_encryption_options.withOptional(false); check(pre + "optional", "false"); + config.server_encryption_options = config.server_encryption_options.withOptional(true); + check(pre + "optional", "true"); check(pre + "client_auth", "false"); config.server_encryption_options = config.server_encryption_options.withRequireClientAuth(true); diff --git a/test/unit/org/apache/cassandra/net/MessagingServiceTest.java b/test/unit/org/apache/cassandra/net/MessagingServiceTest.java index 7bae3fa1a7..606d33637f 100644 --- a/test/unit/org/apache/cassandra/net/MessagingServiceTest.java +++ b/test/unit/org/apache/cassandra/net/MessagingServiceTest.java @@ -25,7 +25,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -35,7 +34,6 @@ import java.util.concurrent.TimeUnit; import java.util.regex.*; import java.util.regex.Matcher; -import com.google.common.collect.Iterables; import com.google.common.net.InetAddresses; import com.codahale.metrics.Timer; @@ -366,11 +364,11 @@ public class MessagingServiceTest for (InboundSockets.InboundSocket socket : connections.sockets()) { Assert.assertEquals(serverEncryptionOptions.isEnabled(), socket.settings.encryption.isEnabled()); - Assert.assertEquals(serverEncryptionOptions.optional, socket.settings.encryption.optional); + Assert.assertEquals(serverEncryptionOptions.isOptional(), socket.settings.encryption.isOptional()); if (!serverEncryptionOptions.isEnabled()) Assert.assertFalse(legacySslPort == socket.settings.bindAddress.port); if (legacySslPort == socket.settings.bindAddress.port) - Assert.assertFalse(socket.settings.encryption.optional); + Assert.assertFalse(socket.settings.encryption.isOptional()); Assert.assertTrue(socket.settings.bindAddress.toString(), expect.remove(socket.settings.bindAddress)); } } diff --git a/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java b/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java index 86b73ab580..e70ef0dd2e 100644 --- a/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java +++ b/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java @@ -23,12 +23,15 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.xml.crypto.Data; + import com.google.common.collect.Sets; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.transport.Server; import org.apache.cassandra.utils.Pair; @@ -38,16 +41,19 @@ import static org.junit.Assert.assertTrue; public class NativeTransportServiceTest { + static EncryptionOptions defaultOptions; + @BeforeClass public static void setupDD() { DatabaseDescriptor.daemonInitialization(); + defaultOptions = DatabaseDescriptor.getNativeProtocolEncryptionOptions(); } @After public void resetConfig() { - DatabaseDescriptor.updateNativeProtocolEncryptionOptions(options -> options.withEnabled(false)); + DatabaseDescriptor.updateNativeProtocolEncryptionOptions(update -> new EncryptionOptions(defaultOptions).applyConfig()); DatabaseDescriptor.setNativeTransportPortSSL(null); } @@ -118,7 +124,7 @@ public class NativeTransportServiceTest { assertEquals(1, service.getServers().size()); Server server = service.getServers().iterator().next(); - assertFalse(server.useSSL); + assertEquals(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED, server.tlsEncryptionPolicy); assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort()); }); } @@ -135,7 +141,7 @@ public class NativeTransportServiceTest service.initialize(); assertEquals(1, service.getServers().size()); Server server = service.getServers().iterator().next(); - assertTrue(server.useSSL); + assertEquals(EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED, server.tlsEncryptionPolicy); assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort()); }, false, 1); } @@ -152,16 +158,19 @@ public class NativeTransportServiceTest service.initialize(); assertEquals(1, service.getServers().size()); Server server = service.getServers().iterator().next(); - assertTrue(server.useSSL); + assertEquals(EncryptionOptions.TlsEncryptionPolicy.OPTIONAL, server.tlsEncryptionPolicy); assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort()); }, false, 1); } @Test - public void testSSLWithNonSSL() + public void testSSLPortWithOptionalEncryption() { // ssl+non-ssl settings: client encryption enabled and additional ssl port specified - DatabaseDescriptor.updateNativeProtocolEncryptionOptions(options -> options.withEnabled(true)); + DatabaseDescriptor.updateNativeProtocolEncryptionOptions( + options -> options.withEnabled(true) + .withOptional(true) + .withKeyStore("test/conf/cassandra_ssl_test.keystore")); DatabaseDescriptor.setNativeTransportPortSSL(8432); withService((NativeTransportService service) -> @@ -170,12 +179,71 @@ public class NativeTransportServiceTest assertEquals(2, service.getServers().size()); assertEquals( Sets.newHashSet(Arrays.asList( - Pair.create(true, DatabaseDescriptor.getNativeTransportPortSSL()), - Pair.create(false, DatabaseDescriptor.getNativeTransportPort()) + Pair.create(EncryptionOptions.TlsEncryptionPolicy.OPTIONAL, + DatabaseDescriptor.getNativeTransportPortSSL()), + Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED, + DatabaseDescriptor.getNativeTransportPort()) ) ), service.getServers().stream().map((Server s) -> - Pair.create(s.useSSL, s.socket.getPort())).collect(Collectors.toSet()) + Pair.create(s.tlsEncryptionPolicy, + s.socket.getPort())).collect(Collectors.toSet()) + ); + }, false, 1); + } + + @Test(expected=java.lang.IllegalStateException.class) + public void testSSLPortWithDisabledEncryption() + { + // ssl+non-ssl settings: client encryption disabled and additional ssl port specified + // should get an illegal state exception + DatabaseDescriptor.updateNativeProtocolEncryptionOptions( + options -> options.withEnabled(false)); + DatabaseDescriptor.setNativeTransportPortSSL(8432); + + withService((NativeTransportService service) -> + { + service.initialize(); + assertEquals(1, service.getServers().size()); + assertEquals( + Sets.newHashSet(Arrays.asList( + Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED, + DatabaseDescriptor.getNativeTransportPort()) + ) + ), + service.getServers().stream().map((Server s) -> + Pair.create(s.tlsEncryptionPolicy, + s.socket.getPort())).collect(Collectors.toSet()) + ); + }, false, 1); + } + + @Test + public void testSSLPortWithEnabledSSL() + { + // ssl+non-ssl settings: client encryption enabled and additional ssl port specified + // encryption is enabled and not optional, so listen on both ports requiring encryption + DatabaseDescriptor.updateNativeProtocolEncryptionOptions( + options -> options.withEnabled(true) + .withOptional(false) + .withKeyStore("test/conf/cassandra_ssl_test.keystore")); + DatabaseDescriptor.setNativeTransportPortSSL(8432); + + withService((NativeTransportService service) -> + { + service.initialize(); + assertEquals(2, service.getServers().size()); + assertEquals( + Sets.newHashSet(Arrays.asList( + Pair.create(EncryptionOptions.TlsEncryptionPolicy.ENCRYPTED, + DatabaseDescriptor.getNativeTransportPortSSL()), + Pair.create(EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED, + DatabaseDescriptor.getNativeTransportPort()) + ) + ), + service.getServers().stream().map((Server s) -> + Pair.create(s.tlsEncryptionPolicy, + s.socket.getPort())).collect(Collectors.toSet()) ); }, false, 1); } diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java index 4ea4cd2f42..5f22a7b7c2 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsTransport.java @@ -40,7 +40,7 @@ public class SettingsTransport implements Serializable public EncryptionOptions getEncryptionOptions() { - EncryptionOptions encOptions = new EncryptionOptions(); + EncryptionOptions encOptions = new EncryptionOptions().applyConfig(); if (options.trustStore.present()) { encOptions = encOptions diff --git a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java index 643e58f911..b054a6e301 100644 --- a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java +++ b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java @@ -70,7 +70,7 @@ public class JavaDriverClient this.username = settings.mode.username; this.password = settings.mode.password; this.authProvider = settings.mode.authProvider; - this.encryptionOptions = encryptionOptions; + this.encryptionOptions = new EncryptionOptions(encryptionOptions).applyConfig(); this.loadBalancingPolicy = loadBalancingPolicy(settings); this.connectionsPerHost = settings.mode.connectionsPerHost == null ? 8 : settings.mode.connectionsPerHost;