You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by dc...@apache.org on 2020/12/03 00:45:04 UTC
[cassandra] branch trunk updated: Bring back the accepted
encryption protocols list as configurable option
This is an automated email from the ASF dual-hosted git repository.
dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 919a896 Bring back the accepted encryption protocols list as configurable option
919a896 is described below
commit 919a8964a83511d96766c3e53ba603e77bca626c
Author: Jon Meredith <jm...@apple.com>
AuthorDate: Wed Dec 2 15:33:36 2020 -0800
Bring back the accepted encryption protocols list as configurable option
patch by Jon Meredith; reviewed by Berenguer Blasi, David Capwell, Dinesh Joshi for CASSANDRA-13325
---
CHANGES.txt | 1 +
.../cassandra/config/DatabaseDescriptor.java | 10 +-
.../apache/cassandra/config/EncryptionOptions.java | 176 ++++++++++++++++----
.../apache/cassandra/db/virtual/SettingsTable.java | 5 +-
.../org/apache/cassandra/security/SSLFactory.java | 181 ++++++++++++++++-----
.../org/apache/cassandra/tools/BulkLoader.java | 2 +-
.../test/AbstractEncryptionOptionsImpl.java | 75 ++++++++-
.../test/InternodeEncryptionOptionsTest.java | 86 ++++++++++
.../test/NativeTransportEncryptionOptionsTest.java | 93 ++++++++++-
.../cassandra/config/EncryptionOptionsTest.java | 12 +-
.../cassandra/db/virtual/SettingsTableTest.java | 17 +-
.../apache/cassandra/security/SSLFactoryTest.java | 21 +--
.../cassandra/stress/util/JavaDriverClient.java | 2 +-
13 files changed, 557 insertions(+), 124 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index b539e2b..172ebcc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -16,6 +16,7 @@
* TLS connections to the storage port on a node without server encryption configured causes java.io.IOException accessing missing keystore (CASSANDRA-16144)
* Internode messaging catches OOMs and does not rethrow (CASSANDRA-15214)
* When a table attempts to clean up metrics, it was cleaning up all global table metrics (CASSANDRA-16095)
+ * Bring back the accepted encryption protocols list as configurable option (CASSANDRA-13325)
Merged from 3.11:
* SASI's `max_compaction_flush_memory_in_mb` settings over 100GB revert to default of 1GB (CASSANDRA-16071)
Merged from 3.0:
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 9f659fd..daac364 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -360,7 +360,7 @@ public class DatabaseDescriptor
applyEncryptionContext();
- applySslContextHotReload();
+ applySslContext();
}
private static void applySimpleConfig()
@@ -988,15 +988,17 @@ public class DatabaseDescriptor
encryptionContext = new EncryptionContext(conf.transparent_data_encryption_options);
}
- public static void applySslContextHotReload()
+ public static void applySslContext()
{
try
{
+ SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, true, true);
+ SSLFactory.validateSslContext("Native transport", conf.client_encryption_options, conf.client_encryption_options.require_client_auth, true);
SSLFactory.initHotReloading(conf.server_encryption_options, conf.client_encryption_options, false);
}
- catch(IOException e)
+ catch (IOException e)
{
- throw new ConfigurationException("Failed to initialize SSL hot reloading", e);
+ throw new ConfigurationException("Failed to initialize SSL", e);
}
}
diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java
index 26bf458..fad6190 100644
--- a/src/java/org/apache/cassandra/config/EncryptionOptions.java
+++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.util.List;
import java.util.Objects;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
@@ -55,7 +56,8 @@ public class EncryptionOptions
public final String truststore;
public final String truststore_password;
public final List<String> cipher_suites;
- public final String protocol;
+ protected String protocol;
+ protected List<String> accepted_protocols;
public final String algorithm;
public final String store_type;
public final boolean require_client_auth;
@@ -79,8 +81,9 @@ public class EncryptionOptions
keystore_password = "cassandra";
truststore = "conf/.truststore";
truststore_password = "cassandra";
- cipher_suites = ImmutableList.of();
- protocol = "TLS";
+ cipher_suites = null;
+ protocol = null;
+ accepted_protocols = null;
algorithm = null;
store_type = "JKS";
require_client_auth = false;
@@ -89,7 +92,7 @@ public class EncryptionOptions
optional = null;
}
- public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> 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<String> cipher_suites, String protocol, List<String> accepted_protocols, 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;
@@ -97,6 +100,7 @@ public class EncryptionOptions
this.truststore_password = truststore_password;
this.cipher_suites = cipher_suites;
this.protocol = protocol;
+ this.accepted_protocols = accepted_protocols;
this.algorithm = algorithm;
this.store_type = store_type;
this.require_client_auth = require_client_auth;
@@ -113,6 +117,7 @@ public class EncryptionOptions
truststore_password = options.truststore_password;
cipher_suites = options.cipher_suites;
protocol = options.protocol;
+ accepted_protocols = options.accepted_protocols;
algorithm = options.algorithm;
store_type = options.store_type;
require_client_auth = options.require_client_auth;
@@ -209,6 +214,83 @@ public class EncryptionOptions
this.optional = optional;
}
+ /**
+ * Sets accepted TLS protocol for this channel. Note that this should only be called by
+ * the configuration parser or tests. It is public only for that purpose, mutating protocol state
+ * is probably a bad idea.
+ * @param protocol value to set
+ */
+ @VisibleForTesting
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ /**
+ * Sets accepted TLS protocols for this channel. Note that this should only be called by
+ * the configuration parser or tests. It is public only for that purpose, mutating protocol state
+ * is probably a bad idea. The function casing is required for snakeyaml to find this setter for the protected field.
+ * @param accepted_protocols value to set
+ */
+ public void setaccepted_protocols(List<String> accepted_protocols) {
+ this.accepted_protocols = accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols);
+ }
+
+ /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+ * it is not a 'default' list or 'support protocol versions' list. It is just an attempt to preserve the
+ * original intent for the user configuration
+ */
+ private final List<String> TLS_PROTOCOL_SUBSTITUTION = ImmutableList.of("TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1");
+
+ /**
+ * Combine the pre-4.0 protocol field with the accepted_protocols list, substituting a list of
+ * explicit protocols for the previous catchall default of "TLS"
+ * @return array of protocol names suitable for passing to SslContextBuilder.protocols, or null if the default
+ */
+ public List<String> acceptedProtocols()
+ {
+ if (accepted_protocols == null)
+ {
+ if (protocol == null)
+ {
+ return null;
+ }
+ // TLS is accepted by SSLContext.getInstance as a shorthand for give me an engine that
+ // can speak some of the TLS protocols. It is not supported by SSLEngine.setAcceptedProtocols
+ // so substitute if the user hasn't provided an accepted protocol configuration
+ else if (protocol.equalsIgnoreCase("TLS"))
+ {
+ return TLS_PROTOCOL_SUBSTITUTION;
+ }
+ else // the user was trying to limit to a single specific protocol, so try that
+ {
+ return ImmutableList.of(protocol);
+ }
+ }
+
+ if (protocol != null && !protocol.equalsIgnoreCase("TLS") &&
+ accepted_protocols.stream().noneMatch(ap -> ap.equalsIgnoreCase(protocol)))
+ {
+ // If the user provided a non-generic default protocol, append it to accepted_protocols - they wanted
+ // it after all.
+ return ImmutableList.<String>builder().addAll(accepted_protocols).add(protocol).build();
+ }
+ else
+ {
+ return accepted_protocols;
+ }
+ }
+
+ public String[] acceptedProtocolsArray()
+ {
+ List<String> ap = acceptedProtocols();
+ return ap == null ? new String[0] : ap.toArray(new String[0]);
+ }
+
+ public String[] cipherSuitesArray()
+ {
+ return cipher_suites == null ? new String[0] : cipher_suites.toArray(new String[0]);
+ }
+
public TlsEncryptionPolicy tlsEncryptionPolicy()
{
if (isOptional())
@@ -228,91 +310,100 @@ public class EncryptionOptions
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional).applyConfig();
}
public EncryptionOptions withCipherSuites(List<String> cipher_suites)
{
return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
- protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional).applyConfig();
}
+
+ public EncryptionOptions withAcceptedProtocols(List<String> accepted_protocols)
+ {
+ return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol,
+ accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols),
+ algorithm, store_type, require_client_auth, require_endpoint_verification, 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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional).applyConfig();
}
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
enabled, optional).applyConfig();
}
@@ -338,6 +429,7 @@ public class EncryptionOptions
Objects.equals(truststore, opt.truststore) &&
Objects.equals(truststore_password, opt.truststore_password) &&
Objects.equals(protocol, opt.protocol) &&
+ Objects.equals(accepted_protocols, opt.accepted_protocols) &&
Objects.equals(algorithm, opt.algorithm) &&
Objects.equals(store_type, opt.store_type) &&
Objects.equals(cipher_suites, opt.cipher_suites);
@@ -356,6 +448,7 @@ public class EncryptionOptions
result += 31 * (truststore == null ? 0 : truststore.hashCode());
result += 31 * (truststore_password == null ? 0 : truststore_password.hashCode());
result += 31 * (protocol == null ? 0 : protocol.hashCode());
+ result += 31 * (accepted_protocols == null ? 0 : accepted_protocols.hashCode());
result += 31 * (algorithm == null ? 0 : algorithm.hashCode());
result += 31 * (store_type == null ? 0 : store_type.hashCode());
result += 31 * (enabled == null ? 0 : Boolean.hashCode(enabled));
@@ -382,9 +475,16 @@ public class EncryptionOptions
this.enable_legacy_ssl_storage_port = false;
}
- public ServerEncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> 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)
+ public ServerEncryptionOptions(String keystore, String keystore_password, String truststore,
+ String truststore_password, List<String> cipher_suites, String protocol,
+ List<String> accepted_protocols, String algorithm, String store_type,
+ boolean require_client_auth, boolean require_endpoint_verification,
+ Boolean 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, null, optional);
+ super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol,
+ accepted_protocols, 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;
}
@@ -404,8 +504,6 @@ public class EncryptionOptions
private ServerEncryptionOptions applyConfigInternal()
{
-
-
super.applyConfig();
isEnabled = this.internode_encryption != InternodeEncryption.none;
@@ -449,98 +547,106 @@ public class EncryptionOptions
public ServerEncryptionOptions withKeyStore(String keystore)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
- protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
public ServerEncryptionOptions withCipherSuites(List<String> cipher_suites)
{
return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
- protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
+ optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
+ }
+
+ public ServerEncryptionOptions withAcceptedProtocols(List<String> accepted_protocols)
+ {
+ return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
+ protocol, accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols),
+ algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
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,
+ protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
}
diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
index e47ce8c..b0ae018 100644
--- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
+++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
@@ -21,6 +21,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
+import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@@ -162,8 +163,8 @@ final class SettingsTable extends AbstractVirtualTable
EncryptionOptions value = (EncryptionOptions) getValue(f);
result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.isEnabled()));
result.row(f.getName() + "_algorithm").column(VALUE, value.algorithm);
- result.row(f.getName() + "_protocol").column(VALUE, value.protocol);
- result.row(f.getName() + "_cipher_suites").column(VALUE, value.cipher_suites.toString());
+ result.row(f.getName() + "_protocol").column(VALUE, Objects.toString(value.acceptedProtocols(), null));
+ result.row(f.getName() + "_cipher_suites").column(VALUE, Objects.toString(value.cipher_suites, null));
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.isOptional()));
diff --git a/src/java/org/apache/cassandra/security/SSLFactory.java b/src/java/org/apache/cassandra/security/SSLFactory.java
index 94db564..7dc0256 100644
--- a/src/java/org/apache/cassandra/security/SSLFactory.java
+++ b/src/java/org/apache/cassandra/security/SSLFactory.java
@@ -31,29 +31,29 @@ import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.ssl.CipherSuiteFilter;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
-import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.util.ReferenceCountUtil;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.Config;
@@ -164,7 +164,6 @@ public final class SSLFactory
/**
* Create a JSSE {@link SSLContext}.
*/
- @SuppressWarnings("resource")
public static SSLContext createSSLContext(EncryptionOptions options, boolean buildTruststore) throws IOException
{
TrustManager[] trustManagers = null;
@@ -175,7 +174,8 @@ public final class SSLFactory
try
{
- SSLContext ctx = SSLContext.getInstance(options.protocol);
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.getDefaultSSLParameters().setProtocols(options.acceptedProtocolsArray());
ctx.init(kmf.getKeyManagers(), trustManagers, null);
return ctx;
}
@@ -233,21 +233,6 @@ public final class SSLFactory
}
}
- public static String[] filterCipherSuites(String[] supported, String[] desired)
- {
- if (Arrays.equals(supported, desired))
- return desired;
- List<String> ldesired = Arrays.asList(desired);
- ImmutableSet<String> ssupported = ImmutableSet.copyOf(supported);
- String[] ret = Iterables.toArray(Iterables.filter(ldesired, Predicates.in(ssupported)), String.class);
- if (desired.length > ret.length && logger.isWarnEnabled())
- {
- Iterable<String> missing = Iterables.filter(ldesired, Predicates.not(Predicates.in(Sets.newHashSet(ret))));
- logger.warn("Filtering out {} as it isn't supported by the socket", Iterables.toString(missing));
- }
- return ret;
- }
-
/**
* get a netty {@link SslContext} instance
*/
@@ -289,6 +274,16 @@ public final class SSLFactory
static SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore,
SocketType socketType, boolean useOpenSsl) throws IOException
{
+ return createNettySslContext(options, buildTruststore, socketType, useOpenSsl,
+ LoggingCipherSuiteFilter.QUIET_FILTER);
+ }
+
+ /**
+ * Create a Netty {@link SslContext} with a supplied cipherFilter
+ */
+ static SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore,
+ SocketType socketType, boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws IOException
+ {
/*
There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
@@ -311,10 +306,12 @@ public final class SSLFactory
builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+ builder.protocols(options.acceptedProtocols());
+
// only set the cipher suites if the opertor has explicity configured values for it; else, use the default
// for each ssl implemention (jdk or openssl)
if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
- builder.ciphers(options.cipher_suites, SupportedCipherSuiteFilter.INSTANCE);
+ builder.ciphers(options.cipher_suites, cipherFilter);
if (buildTruststore)
builder.trustManager(buildTrustManagerFactory(options));
@@ -366,8 +363,6 @@ public final class SSLFactory
logger.debug("Initializing hot reloading SSLContext");
- validateSslCerts(serverOpts, clientOpts);
-
List<HotReloadableFile> fileList = new ArrayList<>();
if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
@@ -397,41 +392,137 @@ public final class SSLFactory
isHotReloadingInitialized = true;
}
-
- /**
- * Sanity checks all certificates to ensure we can actually load them
+ // Non-logging
+ /*
+ * This class will filter all requested ciphers out that are not supported by the current {@link SSLEngine},
+ * logging messages for all dropped ciphers, and throws an exception if no ciphers are supported
*/
- public static void validateSslCerts(EncryptionOptions.ServerEncryptionOptions serverOpts, EncryptionOptions clientOpts) throws IOException
+ public static final class LoggingCipherSuiteFilter implements CipherSuiteFilter
{
- try
+ // Version without logging the ciphers, make sure same filtering logic is used
+ // all the time, regardless of user output.
+ public static final CipherSuiteFilter QUIET_FILTER = new LoggingCipherSuiteFilter();
+ final String settingDescription;
+
+ private LoggingCipherSuiteFilter()
{
- // 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());
- }
+ this.settingDescription = null;
}
- catch (Exception e)
+
+ public LoggingCipherSuiteFilter(String settingDescription)
{
- throw new IOException("Failed to create SSL context using server_encryption_options!", e);
+ this.settingDescription = settingDescription;
}
- try
+
+ @Override
+ public String[] filterCipherSuites(Iterable<String> ciphers, List<String> defaultCiphers,
+ Set<String> supportedCiphers)
{
- // Ensure we're able to create both server & client SslContexts if they might ever be needed
- if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+ Objects.requireNonNull(defaultCiphers, "defaultCiphers");
+ Objects.requireNonNull(supportedCiphers, "supportedCiphers");
+
+ final List<String> newCiphers;
+ if (ciphers == null)
+ {
+ newCiphers = new ArrayList<>(defaultCiphers.size());
+ ciphers = defaultCiphers;
+ }
+ else
{
- createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.SERVER, openSslIsAvailable());
- createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.CLIENT, openSslIsAvailable());
+ newCiphers = new ArrayList<>(supportedCiphers.size());
}
+ for (String c : ciphers)
+ {
+ if (c == null)
+ {
+ break;
+ }
+ if (supportedCiphers.contains(c))
+ {
+ newCiphers.add(c);
+ }
+ else
+ {
+ if (settingDescription != null)
+ {
+ logger.warn("Dropping unsupported cipher_suite {} from {} configuration",
+ c, settingDescription.toLowerCase());
+ }
+ }
+ }
+ if (newCiphers.isEmpty())
+ {
+ throw new IllegalStateException("No ciphers left after filtering supported cipher suite");
+ }
+
+ return newCiphers.toArray(new String[0]);
}
- catch (Exception e)
+ }
+
+ public static void validateSslContext(String contextDescription, EncryptionOptions options, boolean buildTrustStore, boolean logProtocolAndCiphers) throws IOException
+ {
+ if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
{
- throw new IOException("Failed to create SSL context using client_encryption_options!", e);
+ try
+ {
+ CipherSuiteFilter loggingCipherSuiteFilter = logProtocolAndCiphers ? new LoggingCipherSuiteFilter(contextDescription)
+ : LoggingCipherSuiteFilter.QUIET_FILTER;
+ SslContext serverSslContext = createNettySslContext(options, buildTrustStore, SocketType.SERVER, openSslIsAvailable(), loggingCipherSuiteFilter);
+ try
+ {
+ SSLEngine engine = serverSslContext.newEngine(ByteBufAllocator.DEFAULT);
+ try
+ {
+ if (logProtocolAndCiphers)
+ {
+ String[] supportedProtocols = engine.getSupportedProtocols();
+ String[] supportedCiphers = engine.getSupportedCipherSuites();
+ String[] enabledProtocols = engine.getEnabledProtocols();
+ String[] enabledCiphers = engine.getEnabledCipherSuites();
+
+ logger.debug("{} supported TLS protocols: {}", contextDescription,
+ supportedProtocols == null ? "system default" : String.join(", ", supportedProtocols));
+ logger.info("{} enabled TLS protocols: {}", contextDescription,
+ enabledProtocols == null ? "system default" : String.join(", ", enabledProtocols));
+ logger.debug("{} supported cipher suites: {}", contextDescription,
+ supportedCiphers == null ? "system default" : String.join(", ", supportedCiphers));
+ logger.info("{} enabled cipher suites: {}", contextDescription,
+ enabledCiphers == null ? "system default" : String.join(", ", enabledCiphers));
+ }
+ }
+ finally
+ {
+ engine.closeInbound();
+ engine.closeOutbound();
+ ReferenceCountUtil.release(engine);
+ }
+ }
+ finally
+ {
+ ReferenceCountUtil.release(serverSslContext);
+ }
+
+ // Make sure it is possible to build the client context too
+ SslContext clientSslContext = createNettySslContext(options, buildTrustStore, SocketType.CLIENT, openSslIsAvailable());
+ ReferenceCountUtil.release(clientSslContext);
+ }
+ catch (Exception e)
+ {
+ throw new IOException("Failed to create SSL context using " + contextDescription, e);
+ }
}
}
+ /**
+ * Sanity checks all certificates to ensure we can actually load them
+ */
+ public static void validateSslCerts(EncryptionOptions.ServerEncryptionOptions serverOpts, EncryptionOptions clientOpts) throws IOException
+ {
+ validateSslContext("server_encryption_options", serverOpts, true, false);
+ validateSslContext("client_encryption_options", clientOpts, clientOpts.require_client_auth, false);
+ }
+
static class CacheKey
{
private final EncryptionOptions encryptionOptions;
diff --git a/src/java/org/apache/cassandra/tools/BulkLoader.java b/src/java/org/apache/cassandra/tools/BulkLoader.java
index c08881b..5fd3885 100644
--- a/src/java/org/apache/cassandra/tools/BulkLoader.java
+++ b/src/java/org/apache/cassandra/tools/BulkLoader.java
@@ -263,7 +263,7 @@ public class BulkLoader
return JdkSSLOptions.builder()
.withSSLContext(sslContext)
- .withCipherSuites(clientEncryptionOptions.cipher_suites.toArray(new String[0]))
+ .withCipherSuites(clientEncryptionOptions.cipherSuitesArray())
.build();
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
index 6c48299..3ca69c8 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
@@ -18,12 +18,16 @@
package org.apache.cassandra.distributed.test;
+import java.util.Collections;
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 javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
import org.slf4j.Logger;
@@ -51,15 +55,28 @@ import org.apache.cassandra.utils.concurrent.SimpleCondition;
public class AbstractEncryptionOptionsImpl extends TestBaseImpl
{
- Logger logger = LoggerFactory.getLogger(EncryptionOptions.class);
+ final 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";
+
+ // Base configuration map for a valid keystore that can be opened
final static Map<String,Object> validKeystore = ImmutableMap.of("keystore", validKeyStorePath,
- "keystore_password", validKeyStorePassword,
- "truststore", validTrustStorePath,
- "truststore_password", validTrustStorePassword);
+ "keystore_password", validKeyStorePassword,
+ "truststore", validTrustStorePath,
+ "truststore_password", validTrustStorePassword);
+
+ // Configuration with a valid keystore, but an unknown protocol
+ final static Map<String,Object> nonExistantProtocol = ImmutableMap.<String,Object>builder()
+ .putAll(validKeystore)
+ .put("accepted_protocols", Collections.singletonList("NoProtocolIKnow"))
+ .build();
+ // Configuration with a valid keystore, but an unknown cipher suite
+ final static Map<String,Object> nonExistantCipher = ImmutableMap.<String,Object>builder()
+ .putAll(validKeystore)
+ .put("cipher_suites", Collections.singletonList("NoCipherIKnow"))
+ .build();
// Result of a TlsConnection.connect call. The result is updated as the TLS connection
// sequence takes place. The nextOnFailure/nextOnSuccess allows the discard handler
@@ -91,16 +108,32 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
{
final String host;
final int port;
+ final List<String> acceptedProtocols;
+ final List<String> cipherSuites;
final EncryptionOptions encryptionOptions = new EncryptionOptions()
.withEnabled(true)
.withKeyStore(validKeyStorePath).withKeyStorePassword(validKeyStorePassword)
.withTrustStore(validTrustStorePath).withTrustStorePassword(validTrustStorePassword);
private Throwable lastThrowable;
+ private String lastProtocol;
+ private String lastCipher;
public TlsConnection(String host, int port)
{
+ this(host, port, null, null);
+ }
+
+ public TlsConnection(String host, int port, List<String> acceptedProtocols)
+ {
+ this(host, port, acceptedProtocols, null);
+ }
+
+ public TlsConnection(String host, int port, List<String> acceptedProtocols, List<String> cipherSuites)
+ {
this.host = host;
this.port = port;
+ this.acceptedProtocols = acceptedProtocols;
+ this.cipherSuites = cipherSuites;
}
public synchronized Throwable lastThrowable()
@@ -112,6 +145,20 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
lastThrowable = cause;
}
+ public synchronized String lastProtocol()
+ {
+ return lastProtocol;
+ }
+ public synchronized String lastCipher()
+ {
+ return lastCipher;
+ }
+ private synchronized void setProtocolAndCipher(String protocol, String cipher)
+ {
+ lastProtocol = protocol;
+ lastCipher = cipher;
+ }
+
final AtomicReference<ConnectResult> result = new AtomicReference<>(ConnectResult.UNINITIALIZED);
void setResult(String why, ConnectResult expected, ConnectResult newResult)
@@ -144,9 +191,11 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
AtomicInteger connectAttempts = new AtomicInteger(0);
result.set(ConnectResult.UNINITIALIZED);
setLastThrowable(null);
+ setProtocolAndCipher(null, null);
- SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true,
- SSLFactory.SocketType.CLIENT);
+ SslContext sslContext = SSLFactory.getOrCreateSslContext(
+ encryptionOptions.withAcceptedProtocols(acceptedProtocols).withCipherSuites(cipherSuites),
+ true, SSLFactory.SocketType.CLIENT);
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
@@ -160,7 +209,11 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
try
{
logger.debug("handshakeFuture() listener called");
- channelFuture.get();
+ Channel channel = channelFuture.get();
+ SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
+ SSLSession session = sslHandler.engine().getSession();
+ setProtocolAndCipher(session.getProtocol(), session.getCipherSuite());
+
successProgress();
}
catch (Throwable cause)
@@ -268,6 +321,14 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
// verify it was not possible to connect before starting the server
}
}
+
+ void assertReceivedHandshakeException()
+ {
+ Assert.assertTrue("Expected a J8 handshake_failure or J11 protocol_version exception: " + lastThrowable.getMessage(),
+ lastThrowable().getMessage().contains("Received fatal alert: handshake_failure") ||
+ lastThrowable().getMessage().contains("Received fatal alert: protocol_version") ||
+ lastThrowable.getCause() instanceof SSLHandshakeException);
+ }
}
/* Provde the cluster cannot start with the configured options */
diff --git a/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
index 4b05ade..1462b03 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
@@ -19,6 +19,7 @@
package org.apache.cassandra.distributed.test;
import java.net.InetAddress;
+import java.util.Collections;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
@@ -215,4 +216,89 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
}
}
}
+
+ @Test
+ public void negotiatedProtocolMustBeAcceptedProtocolTest() 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", "all")
+ .put("accepted_protocols", Collections.singletonList("TLSv1.1"))
+ .build());
+ }).start())
+ {
+ InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+ int port = cluster.get(1).config().broadcastAddress().getPort();
+
+ TlsConnection tls10Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1"));
+ Assert.assertEquals("Should not be possible to establish a TLSv1 connection",
+ ConnectResult.FAILED_TO_NEGOTIATE, tls10Connection.connect());
+ tls10Connection.assertReceivedHandshakeException();
+
+ TlsConnection tls11Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.1"));
+ Assert.assertEquals("Should be possible to establish a TLSv1.1 connection",
+ ConnectResult.NEGOTIATED, tls11Connection.connect());
+ Assert.assertEquals("TLSv1.1", tls11Connection.lastProtocol());
+
+ TlsConnection tls12Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.2"));
+ Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection",
+ ConnectResult.FAILED_TO_NEGOTIATE, tls12Connection.connect());
+ tls12Connection.assertReceivedHandshakeException();
+ }
+ }
+
+ @Test
+ public void connectionCannotAgreeOnClientAndServer() 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", "all")
+ .put("accepted_protocols", Collections.singletonList("TLSv1.2"))
+ .put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
+ .build());
+ }).start())
+ {
+ InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+ int port = cluster.get(1).config().broadcastAddress().getPort();
+
+ TlsConnection connection = new TlsConnection(address.getHostAddress(), port,
+ Collections.singletonList("TLSv1.2"),
+ Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"));
+ Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection with different ciphers",
+ ConnectResult.FAILED_TO_NEGOTIATE, connection.connect());
+ connection.assertReceivedHandshakeException();
+ }
+ }
+
+ @Test
+ public void nodeMustNotStartWithNonExistantProtocol() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NETWORK);
+ c.set("server_encryption_options",
+ ImmutableMap.<String,Object>builder().putAll(nonExistantProtocol)
+ .put("internode_encryption", "all").build());
+ }).createWithoutStarting())
+ {
+ assertCannotStartDueToConfigurationException(cluster);
+ }
+ }
+
+ @Test
+ public void nodeMustNotStartWithNonExistantCipher() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NETWORK);
+ c.set("server_encryption_options",
+ ImmutableMap.<String,Object>builder().putAll(nonExistantCipher)
+ .put("internode_encryption", "all").build());
+ }).createWithoutStarting())
+ {
+ assertCannotStartDueToConfigurationException(cluster);
+ }
+ }
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
index d64b038..c5a810c 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
@@ -19,6 +19,7 @@
package org.apache.cassandra.distributed.test;
import java.net.InetAddress;
+import java.util.Collections;
import com.google.common.collect.ImmutableMap;
import org.junit.Assert;
@@ -35,10 +36,9 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
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"));
+ 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);
@@ -134,4 +134,89 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
assertCannotStartDueToConfigurationException(cluster);
}
}
+
+
+ @Test
+ public void negotiatedProtocolMustBeAcceptedProtocolTest() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NATIVE_PROTOCOL);
+ c.set("client_encryption_options",
+ ImmutableMap.builder().putAll(validKeystore)
+ .put("enabled", true)
+ .put("accepted_protocols", Collections.singletonList("TLSv1.1"))
+ .build());
+ }).start())
+ {
+ InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+ int port = (int) cluster.get(1).config().get("native_transport_port");
+
+ TlsConnection tls10Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1"));
+ Assert.assertEquals("Should not be possible to establish a TLSv1 connection",
+ ConnectResult.FAILED_TO_NEGOTIATE, tls10Connection.connect());
+ tls10Connection.assertReceivedHandshakeException();
+
+ TlsConnection tls11Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.1"));
+ Assert.assertEquals("Should be possible to establish a TLSv1.1 connection",
+ ConnectResult.NEGOTIATED, tls11Connection.connect());
+ Assert.assertEquals("TLSv1.1", tls11Connection.lastProtocol());
+
+ TlsConnection tls12Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.2"));
+ Assert.assertEquals("Should be possible to establish a TLSv1.2 connection",
+ ConnectResult.FAILED_TO_NEGOTIATE, tls12Connection.connect());
+ tls12Connection.assertReceivedHandshakeException();
+ }
+ }
+
+ @Test
+ public void connectionCannotAgreeOnClientAndServerTest() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NATIVE_PROTOCOL);
+ c.set("client_encryption_options",
+ ImmutableMap.builder().putAll(validKeystore)
+ .put("enabled", true)
+ .put("accepted_protocols", Collections.singletonList("TLSv1.2"))
+ .put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
+ .build());
+ }).start())
+ {
+ InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+ int port = (int) cluster.get(1).config().get("native_transport_port");
+
+ TlsConnection connection = new TlsConnection(address.getHostAddress(), port,
+ Collections.singletonList("TLSv1.2"),
+ Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"));
+ Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection with different ciphers",
+ ConnectResult.FAILED_TO_NEGOTIATE, connection.connect());
+ connection.assertReceivedHandshakeException();
+ }
+ }
+
+ @Test
+ public void nodeMustNotStartWithNonExistantProtocolTest() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NATIVE_PROTOCOL);
+ c.set("client_encryption_options",
+ ImmutableMap.<String,Object>builder().putAll(nonExistantProtocol).put("enabled", true).build());
+ }).createWithoutStarting())
+ {
+ assertCannotStartDueToConfigurationException(cluster);
+ }
+ }
+
+ @Test
+ public void nodeMustNotStartWithNonExistantCiphersTest() throws Throwable
+ {
+ try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+ c.with(Feature.NATIVE_PROTOCOL);
+ c.set("client_encryption_options",
+ ImmutableMap.<String,Object>builder().putAll(nonExistantCipher).put("enabled", true).build());
+ }).createWithoutStarting())
+ {
+ // Should also log "Dropping unsupported cipher_suite NoCipherIKnow from from native transport configuration"
+ assertCannotStartDueToConfigurationException(cluster);
+ }
+ }
}
diff --git a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
index 59638ad..23a6c00 100644
--- a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
+++ b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
@@ -56,16 +56,16 @@ public class EncryptionOptionsTest
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)
+ Collections.emptyList(), null, null, 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 = {
+ static final String absentKeystore = "test/conf/missing-keystore-is-not-here";
+ static final String presentKeystore = "test/conf/keystore.jks";
+ final EncryptionOptionsTestCase[] encryptionOptionTestCases = {
// Optional Keystore Enabled Expected
EncryptionOptionsTestCase.of(null, absentKeystore, false, UNENCRYPTED),
EncryptionOptionsTestCase.of(null, absentKeystore, true, ENCRYPTED),
@@ -106,7 +106,7 @@ public class EncryptionOptionsTest
EncryptionOptions.TlsEncryptionPolicy expected)
{
return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass",
- Collections.emptyList(), "TLS", null, "JKS", false, false, optional, internodeEncryption, false)
+ Collections.emptyList(), null, null, null, "JKS", false, false, optional, internodeEncryption, false)
.applyConfig(),
expected,
String.format("optional=%s keystore=%s internode=%s", optional, keystorePath, internodeEncryption));
@@ -141,7 +141,7 @@ public class EncryptionOptionsTest
.hasMessage("Invalid yaml. Please remove properties [isOptional] from your cassandra.yaml");
}
- ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = {
+ final ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = {
// Optional Keystore Internode Expected
ServerEncryptionOptionsTestCase.of(null, absentKeystore, none, UNENCRYPTED),
diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
index 65297c7..c81da51 100644
--- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
+++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
@@ -145,13 +145,24 @@ public class SettingsTableTest extends CQLTester
config.server_encryption_options = config.server_encryption_options.withAlgorithm("SUPERSSL");
check(pre + "algorithm", "SUPERSSL");
- check(pre + "cipher_suites", "[]");
+ check(pre + "cipher_suites", null);
config.server_encryption_options = config.server_encryption_options.withCipherSuites("c1", "c2");
check(pre + "cipher_suites", "[c1, c2]");
- check(pre + "protocol", config.server_encryption_options.protocol);
+ check(pre + "protocol", null);
config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv5");
- check(pre + "protocol", "TLSv5");
+ check(pre + "protocol", "[TLSv5]");
+
+ config.server_encryption_options = config.server_encryption_options.withProtocol("TLS");
+ check(pre + "protocol", "[TLSv1.3, TLSv1.2, TLSv1.1, TLSv1]");
+
+ config.server_encryption_options = config.server_encryption_options.withProtocol("TLS");
+ config.server_encryption_options = config.server_encryption_options.withAcceptedProtocols(ImmutableList.of("TLSv1.2","TLSv1.1"));
+ check(pre + "protocol", "[TLSv1.2, TLSv1.1]");
+
+ config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv2");
+ config.server_encryption_options = config.server_encryption_options.withAcceptedProtocols(ImmutableList.of("TLSv1.2","TLSv1.1"));
+ check(pre + "protocol", "[TLSv1.2, TLSv1.1, TLSv2]"); // protocol goes after the explicit accept list if non-TLS
check(pre + "optional", "false");
config.server_encryption_options = config.server_encryption_options.withOptional(true);
diff --git a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
index 307a276..4cbc095 100644
--- a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
+++ b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
@@ -55,7 +55,7 @@ public class SSLFactoryTest
}
catch (CertificateException e)
{
- throw new RuntimeException("fialed to create test certs");
+ throw new RuntimeException("failed to create test certs");
}
}
@@ -74,17 +74,6 @@ public class SSLFactoryTest
}
@Test
- public void testFilterCipherSuites()
- {
- String[] supported = new String[] {"x", "b", "c", "f"};
- String[] desired = new String[] { "k", "a", "b", "c" };
- assertArrayEquals(new String[] { "b", "c" }, SSLFactory.filterCipherSuites(supported, desired));
-
- desired = new String[] { "c", "b", "x" };
- assertArrayEquals(desired, SSLFactory.filterCipherSuites(supported, desired));
- }
-
- @Test
public void getSslContext_OpenSSL() throws IOException
{
// only try this test if OpenSsl is available
@@ -174,11 +163,11 @@ public class SSLFactoryTest
.isAvailable());
File keystoreFile = new File(options.keystore);
- SSLFactory.checkCertFilesForHotReloading((ServerEncryptionOptions) options, options);
+ SSLFactory.checkCertFilesForHotReloading(options, options);
keystoreFile.setLastModified(System.currentTimeMillis() + 15000);
- SSLFactory.checkCertFilesForHotReloading((ServerEncryptionOptions) options, options);;
+ SSLFactory.checkCertFilesForHotReloading(options, options);
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, SSLFactory.SocketType.CLIENT, OpenSsl
.isAvailable());
@@ -201,7 +190,7 @@ public class SSLFactoryTest
.withKeyStorePassword("bad password")
.withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all);
- SSLFactory.initHotReloading(options, options, true);
+ SSLFactory.validateSslContext("testSslFactorySslInit_BadPassword_ThrowsException", options, false, true);
}
@Test
@@ -253,7 +242,7 @@ public class SSLFactoryTest
testKeystoreFile.setLastModified(System.currentTimeMillis() + 15000);
FileUtils.forceDelete(testKeystoreFile);
- SSLFactory.checkCertFilesForHotReloading(options, options);;
+ SSLFactory.checkCertFilesForHotReloading(options, options);
SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, SSLFactory.SocketType.CLIENT, OpenSsl
.isAvailable());
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 b054a6e..b934769 100644
--- a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
+++ b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
@@ -144,7 +144,7 @@ public class JavaDriverClient
sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
SSLOptions sslOptions = JdkSSLOptions.builder()
.withSSLContext(sslContext)
- .withCipherSuites(encryptionOptions.cipher_suites.toArray(new String[0])).build();
+ .withCipherSuites(encryptionOptions.cipherSuitesArray()).build();
clusterBuilder.withSSL(sslOptions);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org