diff --git a/CHANGES.txt b/CHANGES.txt index 9021e9de82..c4f606f187 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,7 @@ * Deprecate Pig support (CASSANDRA-10542) * Reduce contention getting instances of CompositeType (CASSANDRA-10433) Merged from 2.1: + * Support encrypted and plain traffic on the same port (CASSANDRA-10559) * Do STCS in DTCS windows (CASSANDRA-10276) * Don't try to get ancestors from half-renamed sstables (CASSANDRA-10501) * Avoid repetition of JVM_OPTS in debian package (CASSANDRA-10251) diff --git a/NEWS.txt b/NEWS.txt index 028d26d546..94567d6b37 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -28,6 +28,11 @@ Operations If moving from the SimpleSnitch, make sure the rack containing all current nodes is named "rack1". +New features +------------ + - Native protocol server now allows both SSL and non-SSL connections on + the same port. + 2.2.3 ===== diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 227d372912..e383c39611 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -827,6 +827,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 diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java index 945a15b72f..31f8b4a82d 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 diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index d610bff975..c56564cacf 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -22,8 +22,8 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.EnumMap; -import java.util.Map; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,6 +34,7 @@ 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.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; @@ -42,6 +43,7 @@ import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.ssl.SslHandler; import io.netty.util.Version; import io.netty.util.concurrent.EventExecutor; @@ -162,8 +164,16 @@ public class Server implements CassandraDaemon.Server final EncryptionOptions.ClientEncryptionOptions clientEnc = DatabaseDescriptor.getClientEncryptionOptions(); if (clientEnc.enabled) { - 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 { @@ -299,12 +309,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; @@ -318,14 +328,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 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); }