Repository: cassandra Updated Branches: refs/heads/trunk e43fbe0a7 -> 71d9dba06
Support encrypted and plain traffic on the same port patch by Norman Maurer; reviewed by Robert Stupp for CASSANDRA-10559 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/535c3ac7 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/535c3ac7 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/535c3ac7 Branch: refs/heads/trunk Commit: 535c3ac759ffd24d2324027bce3d0d6228823ba5 Parents: a639808 Author: Norman Maurer <nor...@apache.org> Authored: Fri Oct 23 14:44:18 2015 +0200 Committer: Robert Stupp <sn...@snazy.de> Committed: Fri Oct 23 14:44:18 2015 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + conf/cassandra.yaml | 2 + .../cassandra/config/EncryptionOptions.java | 1 + .../org/apache/cassandra/transport/Server.java | 75 ++++++++++++++++++-- .../service/NativeTransportServiceTest.java | 18 +++++ 5 files changed, 90 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/535c3ac7/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 0529dd8..67e06ca 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0 + * Support encrypted and plain traffic on the same port (CASSANDRA-10559) * Fix handling of range tombstones when reading old format sstables (CASSANDRA-10360) * Aggregate with Initial Condition fails with C* 3.0 (CASSANDRA-10367) Merged from 2.2: http://git-wip-us.apache.org/repos/asf/cassandra/blob/535c3ac7/conf/cassandra.yaml ---------------------------------------------------------------------- diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index bf5149f..fa9ea47 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -884,6 +884,8 @@ server_encryption_options: # enable or disable client/server encryption. client_encryption_options: enabled: false + # If enabled and optional is set to true encrypted and unencrypted connections are handled. + optional: false keystore: conf/.keystore keystore_password: cassandra # require_client_auth: false http://git-wip-us.apache.org/repos/asf/cassandra/blob/535c3ac7/src/java/org/apache/cassandra/config/EncryptionOptions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java index 945a15b..31f8b4a 100644 --- a/src/java/org/apache/cassandra/config/EncryptionOptions.java +++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java @@ -36,6 +36,7 @@ public abstract class EncryptionOptions public static class ClientEncryptionOptions extends EncryptionOptions { public boolean enabled = false; + public boolean optional = false; } public static class ServerEncryptionOptions extends EncryptionOptions http://git-wip-us.apache.org/repos/asf/cassandra/blob/535c3ac7/src/java/org/apache/cassandra/transport/Server.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index a3cefcd..b786436 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -33,9 +33,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -139,8 +141,16 @@ public class Server implements CassandraDaemon.Server final EncryptionOptions.ClientEncryptionOptions clientEnc = DatabaseDescriptor.getClientEncryptionOptions(); if (this.useSSL) { - logger.info("Enabling encrypted CQL connections between client and server"); - bootstrap.childHandler(new SecureInitializer(this, clientEnc)); + if (clientEnc.optional) + { + logger.info("Enabling optionally encrypted CQL connections between client and server"); + bootstrap.childHandler(new OptionalSecureInitializer(this, clientEnc)); + } + else + { + logger.info("Enabling encrypted CQL connections between client and server"); + bootstrap.childHandler(new SecureInitializer(this, clientEnc)); + } } else { @@ -326,12 +336,12 @@ public class Server implements CassandraDaemon.Server } } - private static class SecureInitializer extends Initializer + protected abstract static class AbstractSecureIntializer extends Initializer { private final SSLContext sslContext; private final EncryptionOptions encryptionOptions; - public SecureInitializer(Server server, EncryptionOptions encryptionOptions) + protected AbstractSecureIntializer(Server server, EncryptionOptions encryptionOptions) { super(server); this.encryptionOptions = encryptionOptions; @@ -345,14 +355,65 @@ public class Server implements CassandraDaemon.Server } } - protected void initChannel(Channel channel) throws Exception - { + protected final SslHandler createSslHandler() { SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); sslEngine.setEnabledCipherSuites(encryptionOptions.cipher_suites); sslEngine.setNeedClientAuth(encryptionOptions.require_client_auth); sslEngine.setEnabledProtocols(SSLFactory.ACCEPTED_PROTOCOLS); - SslHandler sslHandler = new SslHandler(sslEngine); + return new SslHandler(sslEngine); + } + } + + private static class OptionalSecureInitializer extends AbstractSecureIntializer + { + public OptionalSecureInitializer(Server server, EncryptionOptions encryptionOptions) + { + super(server, encryptionOptions); + } + + protected void initChannel(final Channel channel) throws Exception + { + super.initChannel(channel); + channel.pipeline().addFirst("sslDetectionHandler", new ByteToMessageDecoder() + { + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception + { + if (byteBuf.readableBytes() < 5) + { + // To detect if SSL must be used we need to have at least 5 bytes, so return here and try again + // once more bytes a ready. + return; + } + if (SslHandler.isEncrypted(byteBuf)) + { + // Connection uses SSL/TLS, replace the detection handler with a SslHandler and so use + // encryption. + SslHandler sslHandler = createSslHandler(); + channelHandlerContext.pipeline().replace(this, "ssl", sslHandler); + } + else + { + // Connection use no TLS/SSL encryption, just remove the detection handler and continue without + // SslHandler in the pipeline. + channelHandlerContext.pipeline().remove(this); + } + } + }); + } + } + + private static class SecureInitializer extends AbstractSecureIntializer + { + public SecureInitializer(Server server, EncryptionOptions encryptionOptions) + { + super(server, encryptionOptions); + } + + protected void initChannel(Channel channel) throws Exception + { + SslHandler sslHandler = createSslHandler(); super.initChannel(channel); channel.pipeline().addFirst("ssl", sslHandler); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/535c3ac7/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java b/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java index 7eb664f..d0e291a 100644 --- a/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java +++ b/test/unit/org/apache/cassandra/service/NativeTransportServiceTest.java @@ -123,6 +123,24 @@ public class NativeTransportServiceTest { // default ssl settings: client encryption enabled and default native transport port used for ssl only DatabaseDescriptor.getClientEncryptionOptions().enabled = true; + DatabaseDescriptor.getClientEncryptionOptions().optional = false; + + withService((NativeTransportService service) -> + { + service.initialize(); + assertEquals(1, service.getServers().size()); + Server server = service.getServers().iterator().next(); + assertTrue(server.useSSL); + assertEquals(server.socket.getPort(), DatabaseDescriptor.getNativeTransportPort()); + }, false, 1); + } + + @Test + public void testSSLOptional() + { + // default ssl settings: client encryption enabled and default native transport port used for optional ssl + DatabaseDescriptor.getClientEncryptionOptions().enabled = true; + DatabaseDescriptor.getClientEncryptionOptions().optional = true; withService((NativeTransportService service) -> {