diff --git a/CHANGES.txt b/CHANGES.txt index e5f38dbd9a..c0ffd79145 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,7 @@ * Fix SAI's SegmentMetadata min and max primary keys (CASSANDRA-18734) * Remove commons-codec dependency (CASSANDRA-18772) Merged from 4.1: + * Internode legacy SSL storage port certificate is not hot reloaded on update (CASSANDRA-18681) * Nodetool paxos-only repair is no longer incremental (CASSANDRA-18466) Merged from 4.0: * JMH improvements - faster build and async profiler (CASSANDRA-18871) diff --git a/examples/ssl-factory/build.xml b/examples/ssl-factory/build.xml index d803aebd0d..53ba12dc4e 100644 --- a/examples/ssl-factory/build.xml +++ b/examples/ssl-factory/build.xml @@ -20,7 +20,7 @@ - + diff --git a/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java b/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java index b6bd17567c..c76d9b0375 100644 --- a/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java +++ b/src/java/org/apache/cassandra/net/InboundConnectionInitiator.java @@ -65,6 +65,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.INBOUND; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.net.InternodeConnectionUtils.DISCARD_HANDLER_NAME; +import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_FACTORY_CONTEXT_DESCRIPTION; import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_HANDLER_NAME; import static org.apache.cassandra.net.InternodeConnectionUtils.certificates; import static org.apache.cassandra.net.MessagingService.*; @@ -522,7 +523,8 @@ public class InboundConnectionInitiator { final boolean verifyPeerCertificate = true; SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, verifyPeerCertificate, - ISslContextFactory.SocketType.SERVER); + ISslContextFactory.SocketType.SERVER, + SSL_FACTORY_CONTEXT_DESCRIPTION); 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()); diff --git a/src/java/org/apache/cassandra/net/InboundSockets.java b/src/java/org/apache/cassandra/net/InboundSockets.java index 58cd88e6d9..80309aa443 100644 --- a/src/java/org/apache/cassandra/net/InboundSockets.java +++ b/src/java/org/apache/cassandra/net/InboundSockets.java @@ -24,6 +24,8 @@ import java.util.function.Consumer; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -43,6 +45,7 @@ import org.apache.cassandra.utils.concurrent.FutureCombiner; class InboundSockets { + private static Logger logger = LoggerFactory.getLogger(InboundSockets.class); /** * A simple struct to wrap up the components needed for each listening socket. */ @@ -219,6 +222,15 @@ class InboundSockets if (settings.encryption.legacy_ssl_storage_port_enabled) { + // Initialize hot reloading here rather than in org.apache.cassandra.security.SSLFactory.initHotReloading + // as the legacySettings.encryption.sslContextFactory is not shared outside the messaging system. + // Any SslContexts created will be checked when checkCertFilesForHotReloading is called if initialization + // is successful. + try { + legacySettings.encryption.sslContextFactoryInstance.initHotReloading(); + } catch (Throwable tr) { + logger.warn("Unable to initialize hot reloading for legacy internode socket - continuing disabled", tr); + } out.add(new InboundSocket(legacySettings)); /* diff --git a/src/java/org/apache/cassandra/net/InternodeConnectionUtils.java b/src/java/org/apache/cassandra/net/InternodeConnectionUtils.java index fd3d1bd69e..c6c029dfc5 100644 --- a/src/java/org/apache/cassandra/net/InternodeConnectionUtils.java +++ b/src/java/org/apache/cassandra/net/InternodeConnectionUtils.java @@ -38,6 +38,7 @@ public class InternodeConnectionUtils { public static String SSL_HANDLER_NAME = "ssl"; public static String DISCARD_HANDLER_NAME = "discard"; + public static String SSL_FACTORY_CONTEXT_DESCRIPTION = "server_encryption_options"; private static final Logger logger = LoggerFactory.getLogger(InternodeConnectionUtils.class); public static Certificate[] certificates(Channel channel) diff --git a/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java b/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java index b77fa8396e..3b89834a31 100644 --- a/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java +++ b/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java @@ -17,15 +17,12 @@ */ package org.apache.cassandra.net; -import java.io.IOException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.metrics.InternodeOutboundMetrics; import org.apache.cassandra.metrics.MessagingMetrics; @@ -276,12 +273,9 @@ public class MessagingServiceMBeanImpl implements MessagingServiceMBean } @Override - public void reloadSslCertificates() throws IOException + public void reloadSslCertificates() { - final EncryptionOptions.ServerEncryptionOptions serverOpts = DatabaseDescriptor.getInternodeMessagingEncyptionOptions(); - final EncryptionOptions clientOpts = DatabaseDescriptor.getNativeProtocolEncryptionOptions(); - SSLFactory.validateSslCerts(serverOpts, clientOpts); - SSLFactory.checkCertFilesForHotReloading(serverOpts, clientOpts); + SSLFactory.forceCheckCertFiles(); } @Override diff --git a/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java b/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java index 1f1ef6478a..63b99a45db 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java +++ b/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java @@ -66,6 +66,7 @@ import static java.util.concurrent.TimeUnit.*; import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.OUTBOUND; import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.OUTBOUND_PRECONNECT; import static org.apache.cassandra.net.InternodeConnectionUtils.DISCARD_HANDLER_NAME; +import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_FACTORY_CONTEXT_DESCRIPTION; import static org.apache.cassandra.net.InternodeConnectionUtils.SSL_HANDLER_NAME; import static org.apache.cassandra.net.InternodeConnectionUtils.certificates; import static org.apache.cassandra.net.HandshakeProtocol.*; @@ -249,7 +250,7 @@ public class OutboundConnectionInitiator keysToCheck = new ArrayList<>(Collections.list(cachedSslContexts.keys())); + while (!keysToCheck.isEmpty()) { - if (options.sslContextFactoryInstance.shouldReload()) + CacheKey key = keysToCheck.remove(keysToCheck.size()-1); + final EncryptionOptions opts = key.encryptionOptions; + + logger.debug("Checking whether certificates have been updated for {}", key.contextDescription); + if (forceReload || opts.sslContextFactoryInstance.shouldReload()) { - logger.info("SSL certificates have been updated for {}. Resetting the ssl contexts for new " + - "connections.", options.getClass().getName()); - validateSslContext(contextDescription, options, verifyPeerCertificate, false); - clearSslContextCache(options); + try + { + validateSslContext(key.contextDescription, opts, + opts instanceof EncryptionOptions.ServerEncryptionOptions || opts.require_client_auth, false); + logger.info("SSL certificates have been updated for {}. Resetting the ssl contexts for new " + + "connections.", key.contextDescription); + clearSslContextCache(key.encryptionOptions, keysToCheck); + } + catch (Throwable tr) + { + logger.error("Failed to hot reload the SSL Certificates! Please check the certificate files for {}.", + key.contextDescription, tr); + } } } - catch(Exception e) - { - logger.error("Failed to hot reload the SSL Certificates! Please check the certificate files.", e); - } } /** @@ -226,12 +232,16 @@ public final class SSLFactory cachedSslContexts.clear(); } - private static void clearSslContextCache(EncryptionOptions options) + /** + * Clear all cachedSslContexts with this encryption option and remove them from future keys to check + */ + private static void clearSslContextCache(EncryptionOptions options, List keysToCheck) { cachedSslContexts.forEachKey(1, cacheKey -> { - if (cacheKey.encryptionOptions.equals(options)) + if (Objects.equals(options, cacheKey.encryptionOptions)) { cachedSslContexts.remove(cacheKey); + keysToCheck.remove(cacheKey); } }); } @@ -262,9 +272,7 @@ public final class SSLFactory if (!isHotReloadingInitialized) { ScheduledExecutors.scheduledTasks - .scheduleWithFixedDelay(() -> checkCertFilesForHotReloading( - DatabaseDescriptor.getInternodeMessagingEncyptionOptions(), - DatabaseDescriptor.getNativeProtocolEncryptionOptions()), + .scheduleWithFixedDelay(SSLFactory::checkCertFilesForHotReloading, DEFAULT_HOT_RELOAD_INITIAL_DELAY_SEC, DEFAULT_HOT_RELOAD_PERIOD_SEC, TimeUnit.SECONDS); } @@ -421,11 +429,13 @@ public final class SSLFactory { private final EncryptionOptions encryptionOptions; private final SocketType socketType; + private final String contextDescription; - public CacheKey(EncryptionOptions encryptionOptions, SocketType socketType) + public CacheKey(EncryptionOptions encryptionOptions, SocketType socketType, String contextDescription) { this.encryptionOptions = encryptionOptions; this.socketType = socketType; + this.contextDescription = contextDescription; } public boolean equals(Object o) @@ -434,7 +444,8 @@ public final class SSLFactory if (o == null || getClass() != o.getClass()) return false; CacheKey cacheKey = (CacheKey) o; return (socketType == cacheKey.socketType && - Objects.equals(encryptionOptions, cacheKey.encryptionOptions)); + Objects.equals(encryptionOptions, cacheKey.encryptionOptions) && + Objects.equals(contextDescription, cacheKey.contextDescription)); } public int hashCode() @@ -442,6 +453,7 @@ public final class SSLFactory int result = 0; result += 31 * socketType.hashCode(); result += 31 * encryptionOptions.hashCode(); + result += 31 * contextDescription.hashCode(); return result; } } diff --git a/src/java/org/apache/cassandra/transport/PipelineConfigurator.java b/src/java/org/apache/cassandra/transport/PipelineConfigurator.java index ff524785dc..d19af211e2 100644 --- a/src/java/org/apache/cassandra/transport/PipelineConfigurator.java +++ b/src/java/org/apache/cassandra/transport/PipelineConfigurator.java @@ -66,6 +66,8 @@ public class PipelineConfigurator // which will throttle a system under any normal load. private static final boolean DEBUG = TEST_UNSAFE_VERBOSE_DEBUG_CLIENT_PROTOCOL.getBoolean(); + public static final String SSL_FACTORY_CONTEXT_DESCRIPTION = "client_encryption_options"; + // Stateless handlers private static final ConnectionLimitHandler connectionLimitHandler = new ConnectionLimitHandler(); @@ -167,7 +169,8 @@ public class PipelineConfigurator return channel -> { SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, encryptionOptions.require_client_auth, - ISslContextFactory.SocketType.SERVER); + ISslContextFactory.SocketType.SERVER, + SSL_FACTORY_CONTEXT_DESCRIPTION); channel.pipeline().addFirst(SSL_HANDLER, new ByteToMessageDecoder() { @@ -202,7 +205,8 @@ public class PipelineConfigurator return channel -> { SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, encryptionOptions.require_client_auth, - ISslContextFactory.SocketType.SERVER); + ISslContextFactory.SocketType.SERVER, + SSL_FACTORY_CONTEXT_DESCRIPTION); InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? (InetSocketAddress) channel.remoteAddress() : null; channel.pipeline().addFirst(SSL_HANDLER, newSslHandler(channel, sslContext, peer)); }; diff --git a/src/java/org/apache/cassandra/transport/SimpleClient.java b/src/java/org/apache/cassandra/transport/SimpleClient.java index 2b57d104e4..8e270a3f55 100644 --- a/src/java/org/apache/cassandra/transport/SimpleClient.java +++ b/src/java/org/apache/cassandra/transport/SimpleClient.java @@ -55,6 +55,7 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static org.apache.cassandra.net.SocketFactory.newSslHandler; import static org.apache.cassandra.transport.CQLMessageHandler.envelopeSize; import static org.apache.cassandra.transport.Flusher.MAX_FRAMED_PAYLOAD_SIZE; +import static org.apache.cassandra.transport.PipelineConfigurator.SSL_FACTORY_CONTEXT_DESCRIPTION; import static org.apache.cassandra.utils.concurrent.NonBlockingRateLimiter.NO_OP_LIMITER; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; @@ -624,7 +625,7 @@ public class SimpleClient implements Closeable { super.initChannel(channel); SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, encryptionOptions.require_client_auth, - ISslContextFactory.SocketType.CLIENT); + ISslContextFactory.SocketType.CLIENT, SSL_FACTORY_CONTEXT_DESCRIPTION); InetSocketAddress peer = encryptionOptions.require_endpoint_verification ? new InetSocketAddress(host, port) : null; channel.pipeline().addFirst("ssl", newSslHandler(channel, sslContext, peer)); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java index 1c5ddbf6b5..b488867433 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java +++ b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java @@ -200,7 +200,7 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl SslContext sslContext = SSLFactory.getOrCreateSslContext( encryptionOptions.withAcceptedProtocols(acceptedProtocols).withCipherSuites(cipherSuites), - true, ISslContextFactory.SocketType.CLIENT); + true, ISslContextFactory.SocketType.CLIENT, "test"); EventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); diff --git a/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java index bcd13efd4d..c169b9e775 100644 --- a/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java @@ -73,7 +73,7 @@ public class DefaultSslContextFactoryTest .withOutboundKeystorePassword("cassandra") .withRequireClientAuth(false) .withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA"); - SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); Assert.assertNotNull(sslContext); if (OpenSsl.isAvailable()) Assert.assertTrue(sslContext instanceof OpenSslContext); diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java index ee9c6104c8..a1fb37c1b9 100644 --- a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java @@ -218,7 +218,7 @@ public class PEMBasedSslContextFactoryTest .withRequireClientAuth(false) .withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA") .withSslContextFactory(sslContextFactory); - SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.SERVER); + SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.SERVER, "test"); Assert.assertNotNull(sslContext); if (OpenSsl.isAvailable()) Assert.assertTrue(sslContext instanceof OpenSslContext); @@ -239,7 +239,7 @@ public class PEMBasedSslContextFactoryTest .withRequireClientAuth(false) .withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA") .withSslContextFactory(sslContextFactory); - SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); Assert.assertNotNull(sslContext); if (OpenSsl.isAvailable()) Assert.assertTrue(sslContext instanceof OpenSslContext); diff --git a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java index ff3bab9d26..b6e0b8e4b5 100644 --- a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java @@ -19,6 +19,7 @@ package org.apache.cassandra.security; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -76,6 +77,7 @@ public class SSLFactoryTest @Before public void setup() { + SSLFactory.clearSslContextCache(); encryptionOptions = new ServerEncryptionOptions() .withTrustStore("test/conf/cassandra_ssl_test.truststore") .withTrustStorePassword("cassandra") @@ -112,20 +114,24 @@ public class SSLFactoryTest { ServerEncryptionOptions options = addKeystoreOptions(encryptionOptions) .withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); + ServerEncryptionOptions legacyOptions = options.withOptional(false).withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); + options.sslContextFactoryInstance.initHotReloading(); + legacyOptions.sslContextFactoryInstance.initHotReloading(); - SSLFactory.initHotReloading(options, options, true); - - SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); File keystoreFile = new File(options.keystore); - SSLFactory.checkCertFilesForHotReloading(options, options); + SSLFactory.checkCertFilesForHotReloading(); keystoreFile.trySetLastModified(System.currentTimeMillis() + 15000); - SSLFactory.checkCertFilesForHotReloading(options, options); - SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SSLFactory.checkCertFilesForHotReloading(); + SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); Assert.assertNotSame(oldCtx, newCtx); + Assert.assertNotSame(oldLegacyCtx, newLegacyCtx); } catch (Exception e) { @@ -177,21 +183,26 @@ public class SSLFactoryTest try { ServerEncryptionOptions options = addPEMKeystoreOptions(encryptionOptions) - .withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); + .withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.dc); + // emulate InboundSockets and share the cert but with different options, no extra hot reloading init + ServerEncryptionOptions legacyOptions = options.withOptional(false).withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); + options.sslContextFactoryInstance.initHotReloading(); + legacyOptions.sslContextFactoryInstance.initHotReloading(); - SSLFactory.initHotReloading(options, options, true); - - SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); File keystoreFile = new File(options.keystore); - SSLFactory.checkCertFilesForHotReloading(options, options); + SSLFactory.checkCertFilesForHotReloading(); keystoreFile.trySetLastModified(System.currentTimeMillis() + 15000); - SSLFactory.checkCertFilesForHotReloading(options, options); - SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SSLFactory.checkCertFilesForHotReloading(); + SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); Assert.assertNotSame(oldCtx, newCtx); + Assert.assertNotSame(oldLegacyCtx, newLegacyCtx); } catch (Exception e) { @@ -219,20 +230,26 @@ public class SSLFactoryTest try { ServerEncryptionOptions options = addKeystoreOptions(encryptionOptions); + // emulate InboundSockets and share the cert but with different options, no extra hot reloading init + ServerEncryptionOptions legacyOptions = options.withOptional(false).withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all); - SSLFactory.initHotReloading(options, options, true); - SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); - File keystoreFile = new File(options.keystore); + File testKeystoreFile = new File(options.keystore + ".test"); + FileUtils.copyFile(new File(options.keystore).toJavaIOFile(), testKeystoreFile.toJavaIOFile()); + options = options.withKeyStore(testKeystoreFile.path()); - SSLFactory.checkCertFilesForHotReloading(options, options); - keystoreFile.trySetLastModified(System.currentTimeMillis() + 5000); + SSLFactory.initHotReloading(options, options, true); // deliberately not initializing with legacyOptions to match InboundSockets.addBindings - ServerEncryptionOptions modOptions = new ServerEncryptionOptions(options) - .withKeyStorePassword("bad password"); - SSLFactory.checkCertFilesForHotReloading(modOptions, modOptions); - SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext oldLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); + + changeKeystorePassword(options.keystore, options.keystore_password, "bad password"); + + SSLFactory.checkCertFilesForHotReloading(); + SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SslContext newLegacyCtx = SSLFactory.getOrCreateSslContext(legacyOptions, true, ISslContextFactory.SocketType.CLIENT, "test legacy"); Assert.assertSame(oldCtx, newCtx); + Assert.assertSame(oldLegacyCtx, newLegacyCtx); } finally { @@ -253,14 +270,14 @@ public class SSLFactoryTest SSLFactory.initHotReloading(options, options, true); - SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); - SSLFactory.checkCertFilesForHotReloading(options, options); + SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); + SSLFactory.checkCertFilesForHotReloading(); testKeystoreFile.trySetLastModified(System.currentTimeMillis() + 15000); FileUtils.forceDelete(testKeystoreFile.toJavaIOFile()); - SSLFactory.checkCertFilesForHotReloading(options, options); - SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT); + SSLFactory.checkCertFilesForHotReloading(); + SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT, "test"); Assert.assertSame(oldCtx, newCtx); } @@ -282,7 +299,7 @@ public class SSLFactoryTest .withCipherSuites("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); SslContext ctx1 = SSLFactory.getOrCreateSslContext(options, true, - ISslContextFactory.SocketType.SERVER); + ISslContextFactory.SocketType.SERVER, "test"); Assert.assertTrue(ctx1.isServer()); Assert.assertEquals(ctx1.cipherSuites(), options.cipher_suites); @@ -290,7 +307,7 @@ public class SSLFactoryTest options = options.withCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); SslContext ctx2 = SSLFactory.getOrCreateSslContext(options, true, - ISslContextFactory.SocketType.CLIENT); + ISslContextFactory.SocketType.CLIENT, "test"); Assert.assertTrue(ctx2.isClient()); Assert.assertEquals(ctx2.cipherSuites(), options.cipher_suites); @@ -309,7 +326,7 @@ public class SSLFactoryTest .withRequireClientAuth(true) .withRequireEndpointVerification(false); - SSLFactory.CacheKey cacheKey1 = new SSLFactory.CacheKey(encryptionOptions1, ISslContextFactory.SocketType.SERVER + SSLFactory.CacheKey cacheKey1 = new SSLFactory.CacheKey(encryptionOptions1, ISslContextFactory.SocketType.SERVER, "test" ); Map parameters2 = new HashMap<>(); @@ -322,7 +339,7 @@ public class SSLFactoryTest .withRequireClientAuth(true) .withRequireEndpointVerification(false); - SSLFactory.CacheKey cacheKey2 = new SSLFactory.CacheKey(encryptionOptions2, ISslContextFactory.SocketType.SERVER + SSLFactory.CacheKey cacheKey2 = new SSLFactory.CacheKey(encryptionOptions2, ISslContextFactory.SocketType.SERVER, "test" ); Assert.assertEquals(cacheKey1, cacheKey2); @@ -339,7 +356,7 @@ public class SSLFactoryTest .withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters1)) .withProtocol("TLSv1.1"); - SSLFactory.CacheKey cacheKey1 = new SSLFactory.CacheKey(encryptionOptions1, ISslContextFactory.SocketType.SERVER + SSLFactory.CacheKey cacheKey1 = new SSLFactory.CacheKey(encryptionOptions1, ISslContextFactory.SocketType.SERVER, "test" ); Map parameters2 = new HashMap<>(); @@ -350,7 +367,7 @@ public class SSLFactoryTest .withSslContextFactory(new ParameterizedClass(DummySslContextFactoryImpl.class.getName(), parameters2)) .withProtocol("TLSv1.1"); - SSLFactory.CacheKey cacheKey2 = new SSLFactory.CacheKey(encryptionOptions2, ISslContextFactory.SocketType.SERVER + SSLFactory.CacheKey cacheKey2 = new SSLFactory.CacheKey(encryptionOptions2, ISslContextFactory.SocketType.SERVER, "test" ); Assert.assertNotEquals(cacheKey1, cacheKey2); @@ -387,4 +404,26 @@ public class SSLFactoryTest final Certificate[] certificates = keyManager1.getCertificateChain("cassandra_ssl_test"); return certificates[0]; } + + void changeKeystorePassword(String filename, String currentPassword, String newPassword) + { + try + { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + char[] loadPasswd = currentPassword.toCharArray(); + char[] storePasswd = newPassword.toCharArray(); + try (FileInputStream is = new FileInputStream(filename)) + { + keystore.load(is, loadPasswd); + } + try (FileOutputStream os = new FileOutputStream(filename)) + { + keystore.store(os, storePasswd); + } + } + catch (Throwable tr) + { + throw new RuntimeException(tr); + } + } }