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

Reply via email to