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 2021/01/07 22:42:32 UTC

[cassandra] branch trunk updated: SSLFactory should initialize SSLContext before setting protocols

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 7637acc  SSLFactory should initialize SSLContext before setting protocols
7637acc is described below

commit 7637acc3d762047f2a478855eb4d239b4f314cd8
Author: Jon Meredith <jm...@apple.com>
AuthorDate: Thu Jan 7 13:10:53 2021 -0800

    SSLFactory should initialize SSLContext before setting protocols
    
    patch by Jon Meredith; reviewed by David Capwell, Dinesh Joshi for CASSANDRA-16362
---
 CHANGES.txt                                        |   1 +
 .../apache/cassandra/config/EncryptionOptions.java |   6 +-
 .../org/apache/cassandra/security/SSLFactory.java  |  21 +++-
 .../org/apache/cassandra/tools/BulkLoader.java     |  24 ++++-
 .../test/SSTableLoaderEncryptionOptionsTest.java   | 106 +++++++++++++++++++++
 .../config/DatabaseDescriptorRefTest.java          |   1 +
 .../cassandra/db/virtual/SettingsTableTest.java    |   3 +-
 .../cassandra/stress/util/JavaDriverClient.java    |  22 ++++-
 8 files changed, 171 insertions(+), 13 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index b593a1b..10e7a43 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,7 @@
 4.0-beta5
  * Restore sasi dependencies jflex, snowball-stemmer, and concurrent-trees, in the cassandra-all pom (CASSANDRA-16303)
  * Fix DecimalDeserializer#toString OOM (CASSANDRA-14925)
+ * SSLFactory should initialize SSLContext before setting protocols (CASSANDRA-16362)
 
 4.0-beta4
  * DROP COMPACT STORAGE should invalidate prepared statements still using CompactTableMetadata (CASSANDRA-16361)
diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java
index fad6190..a534e36 100644
--- a/src/java/org/apache/cassandra/config/EncryptionOptions.java
+++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.locator.IEndpointSnitch;
 import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.security.SSLFactory;
 
 public class EncryptionOptions
 {
@@ -236,10 +237,9 @@ public class EncryptionOptions
     }
 
     /* 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
+     * by extracting it from the default "TLS" SSL Context instance
      */
-    private final List<String> TLS_PROTOCOL_SUBSTITUTION = ImmutableList.of("TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1");
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
 
     /**
      * Combine the pre-4.0 protocol field with the accepted_protocols list, substituting a list of
diff --git a/src/java/org/apache/cassandra/security/SSLFactory.java b/src/java/org/apache/cassandra/security/SSLFactory.java
index 7dc0256..2f7ea75 100644
--- a/src/java/org/apache/cassandra/security/SSLFactory.java
+++ b/src/java/org/apache/cassandra/security/SSLFactory.java
@@ -161,6 +161,26 @@ public final class SSLFactory
         }
     }
 
+    /** Provides the list of protocols that would have been supported if "TLS" was selected as the
+     * protocol before the change for CASSANDRA-13325 that expects explicit protocol versions.
+     * @return list of enabled protocol names
+     */
+    public static List<String> tlsInstanceProtocolSubstitution()
+    {
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(null, null, null);
+            SSLParameters params = ctx.getSupportedSSLParameters();
+            String[] protocols = params.getProtocols();
+            return Arrays.asList(protocols);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("Error finding supported TLS Protocols", e);
+        }
+    }
+
     /**
      * Create a JSSE {@link SSLContext}.
      */
@@ -175,7 +195,6 @@ public final class SSLFactory
         try
         {
             SSLContext ctx = SSLContext.getInstance("TLS");
-            ctx.getDefaultSSLParameters().setProtocols(options.acceptedProtocolsArray());
             ctx.init(kmf.getKeyManagers(), trustManagers, null);
             return ctx;
         }
diff --git a/src/java/org/apache/cassandra/tools/BulkLoader.java b/src/java/org/apache/cassandra/tools/BulkLoader.java
index 5fd3885..c1de1ff 100644
--- a/src/java/org/apache/cassandra/tools/BulkLoader.java
+++ b/src/java/org/apache/cassandra/tools/BulkLoader.java
@@ -21,15 +21,17 @@ import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.Set;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
 
 import com.datastax.driver.core.AuthProvider;
-import com.datastax.driver.core.JdkSSLOptions;
+import com.datastax.driver.core.RemoteEndpointAwareJdkSSLOptions;
 import com.datastax.driver.core.SSLOptions;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 
+import com.datastax.shaded.netty.channel.socket.SocketChannel;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.config.EncryptionOptions;
 import org.apache.cassandra.io.sstable.SSTableLoader;
@@ -261,10 +263,22 @@ public class BulkLoader
             throw new RuntimeException("Could not create SSL Context.", e);
         }
 
-        return JdkSSLOptions.builder()
-                            .withSSLContext(sslContext)
-                            .withCipherSuites(clientEncryptionOptions.cipherSuitesArray())
-                            .build();
+        // Temporarily override newSSLEngine to set accepted protocols until it is added to
+        // RemoteEndpointAwareJdkSSLOptions.  See CASSANDRA-13325 and CASSANDRA-16362.
+        RemoteEndpointAwareJdkSSLOptions sslOptions = new RemoteEndpointAwareJdkSSLOptions(sslContext, null)
+        {
+            protected SSLEngine newSSLEngine(SocketChannel channel, InetSocketAddress remoteEndpoint)
+            {
+                SSLEngine engine = super.newSSLEngine(channel, remoteEndpoint);
+
+                String[] acceptedProtocols = clientEncryptionOptions.acceptedProtocolsArray();
+                if (acceptedProtocols != null && acceptedProtocols.length > 0)
+                    engine.setEnabledProtocols(acceptedProtocols);
+
+                return engine;
+            }
+        };
+        return sslOptions;
     }
 
     static class ExternalClient extends NativeSSTableLoaderClient
diff --git a/test/distributed/org/apache/cassandra/distributed/test/SSTableLoaderEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/SSTableLoaderEncryptionOptionsTest.java
new file mode 100644
index 0000000..1ddc1fb
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/distributed/test/SSTableLoaderEncryptionOptionsTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.io.IOException;
+import java.util.Collections;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.tools.BulkLoader;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+
+public class SSTableLoaderEncryptionOptionsTest extends AbstractEncryptionOptionsImpl
+{
+    static Cluster CLUSTER;
+    static String NODES;
+    static int NATIVE_PORT;
+    static int STORAGE_PORT;
+
+    @BeforeClass
+    public static void setupCluster() throws IOException
+    {
+        CLUSTER = Cluster.build().withNodes(1).withConfig(c -> {
+            c.with(Feature.NATIVE_PROTOCOL, Feature.NETWORK, Feature.GOSSIP); // need gossip to get hostid for java driver
+            c.set("server_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("internode_encryption", "all")
+                              .put("optional", false)
+                              .build());
+            c.set("client_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("enabled", true)
+                              .put("optional", false)
+                              .put("accepted_protocols", Collections.singletonList("TLSv1.2"))
+                              .build());
+        }).start();
+        NODES = CLUSTER.get(1).config().broadcastAddress().getHostString();
+        NATIVE_PORT = CLUSTER.get(1).callOnInstance(DatabaseDescriptor::getNativeTransportPort);
+        STORAGE_PORT = CLUSTER.get(1).callOnInstance(DatabaseDescriptor::getStoragePort);
+    }
+
+    @AfterClass
+    public static void tearDownCluster()
+    {
+        if (CLUSTER != null)
+            CLUSTER.close();
+    }
+    @Test
+    public void bulkLoaderSuccessfullyConnectsOverSsl() throws Throwable
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeClass(BulkLoader.class,
+                                                            "--nodes", NODES,
+                                                            "--port", Integer.toString(NATIVE_PORT),
+                                                            "--storage-port", Integer.toString(STORAGE_PORT),
+                                                            "--keystore", validKeyStorePath,
+                                                            "--keystore-password", validKeyStorePassword,
+                                                            "--truststore", validTrustStorePath,
+                                                            "--truststore-password", validTrustStorePassword,
+                                                            "test/data/legacy-sstables/na/legacy_tables/legacy_na_clust");
+        tool.assertOnCleanExit();
+        assertTrue(tool.getStdout().contains("Summary statistics"));
+    }
+
+    @Test
+    public void bulkLoaderCannotAgreeOnClientTLSProtocol()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeClass(BulkLoader.class,
+                                                            "--ssl-protocol", "TLSv1",
+                                                            "--nodes", NODES,
+                                                            "--port", Integer.toString(NATIVE_PORT),
+                                                            "--storage-port", Integer.toString(STORAGE_PORT),
+                                                            "--keystore", validKeyStorePath,
+                                                            "--keystore-password", validKeyStorePassword,
+                                                            "--truststore", validTrustStorePath,
+                                                            "--truststore-password", validTrustStorePassword,
+                                                            "test/data/legacy-sstables/na/legacy_tables/legacy_na_clust");
+        assertNotEquals(0, tool.getExitCode());
+        assertTrue(tool.getStdout().contains("SSLHandshakeException"));
+    }
+}
diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
index 7dc84f6..521071c 100644
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
@@ -141,6 +141,7 @@ public class DatabaseDescriptorRefTest
     "org.apache.cassandra.locator.Replica",
     "org.apache.cassandra.locator.SimpleSeedProvider",
     "org.apache.cassandra.locator.SeedProvider",
+    "org.apache.cassandra.security.SSLFactory",
     "org.apache.cassandra.security.EncryptionContext",
     "org.apache.cassandra.service.CacheService$CacheType",
     "org.apache.cassandra.utils.binlog.BinLogOptions",
diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
index c81da51..a2fda49 100644
--- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
+++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
@@ -36,6 +36,7 @@ import org.apache.cassandra.config.Config;
 import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption;
 import org.apache.cassandra.config.ParameterizedClass;
 import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.security.SSLFactory;
 
 public class SettingsTableTest extends CQLTester
 {
@@ -154,7 +155,7 @@ public class SettingsTableTest extends CQLTester
         check(pre + "protocol", "[TLSv5]");
 
         config.server_encryption_options = config.server_encryption_options.withProtocol("TLS");
-        check(pre + "protocol", "[TLSv1.3, TLSv1.2, TLSv1.1, TLSv1]");
+        check(pre + "protocol", SSLFactory.tlsInstanceProtocolSubstitution().toString());
 
         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"));
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 b934769..49cb0a8 100644
--- a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
+++ b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
@@ -17,16 +17,19 @@
  */
 package org.apache.cassandra.stress.util;
 
+import java.net.InetSocketAddress;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
 
 import com.datastax.driver.core.*;
 import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy;
 import com.datastax.driver.core.policies.LoadBalancingPolicy;
 import com.datastax.driver.core.policies.TokenAwarePolicy;
 import com.datastax.driver.core.policies.WhiteListPolicy;
+import com.datastax.shaded.netty.channel.socket.SocketChannel;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 import io.netty.util.internal.logging.Slf4JLoggerFactory;
 import org.apache.cassandra.config.EncryptionOptions;
@@ -142,9 +145,22 @@ public class JavaDriverClient
         {
             SSLContext sslContext;
             sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
-            SSLOptions sslOptions = JdkSSLOptions.builder()
-                                                 .withSSLContext(sslContext)
-                                                 .withCipherSuites(encryptionOptions.cipherSuitesArray()).build();
+
+            // Temporarily override newSSLEngine to set accepted protocols until it is added to
+            // RemoteEndpointAwareJdkSSLOptions.  See CASSANDRA-13325 and CASSANDRA-16362.
+            RemoteEndpointAwareJdkSSLOptions sslOptions = new RemoteEndpointAwareJdkSSLOptions(sslContext, null)
+            {
+                protected SSLEngine newSSLEngine(SocketChannel channel, InetSocketAddress remoteEndpoint)
+                {
+                    SSLEngine engine = super.newSSLEngine(channel, remoteEndpoint);
+
+                    String[] acceptedProtocols = encryptionOptions.acceptedProtocolsArray();
+                    if (acceptedProtocols != null && acceptedProtocols.length > 0)
+                        engine.setEnabledProtocols(acceptedProtocols);
+
+                    return engine;
+                }
+            };
             clusterBuilder.withSSL(sslOptions);
         }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org