SSLFactory should initialize SSLContext before setting protocols

patch by Jon Meredith; reviewed by David Capwell, Dinesh Joshi for CASSANDRA-16362
This commit is contained in:
Jon Meredith 2021-01-07 13:10:53 -08:00 committed by David Capwell
parent 2ef0ac6ae9
commit 7637acc3d7
8 changed files with 171 additions and 13 deletions

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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"));
}
}

View File

@ -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",

View File

@ -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"));

View File

@ -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);
}