Skip to content

Commit

Permalink
Add SSL support
Browse files Browse the repository at this point in the history
  • Loading branch information
olim7t committed Apr 4, 2017
1 parent 9d3e501 commit b8394cc
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 24 deletions.
Expand Up @@ -27,7 +27,11 @@ public enum CoreDriverOption implements DriverOption {

AUTHENTICATION_PROVIDER_CLASS("authentication.provider-class", false),
AUTHENTICATION_CONFIG_USERNAME("authentication.config.username", false),
AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false);
AUTHENTICATION_CONFIG_PASSWORD("authentication.config.password", false),

SSL_FACTORY_CLASS("ssl.factory-class", false),
SSL_CONFIG_CIPHER_SUITES("ssl.config.cipher-suites", false),
;

private final String path;
private final boolean required;
Expand Down
Expand Up @@ -15,6 +15,7 @@
*/
package com.datastax.oss.driver.api.core.config;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -29,6 +30,8 @@ public interface DriverConfigProfile {

String getString(DriverOption option);

List<String> getStringList(DriverOption option);

long getBytes(DriverOption option);

long getDuration(DriverOption option, TimeUnit targetUnit);
Expand Down
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2017-2017 DataStax Inc.
*
* Licensed 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 com.datastax.oss.driver.api.core.ssl;

import com.datastax.oss.driver.api.core.config.CoreDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

/**
* Default SSL implementation.
*
* <p>To activate this class, an {@code ssl} section must be included in the driver configuration,
* for example:
*
* <pre>
* datastax-java-driver {
* ssl {
* factory-class = com.datastax.driver.api.core.ssl.DefaultSslEngineFactory
* config {
* cipher-suites = [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
* }
* }
* }
* </pre>
*
* See the {@code reference.conf} file included with the driver for more information.
*/
public class DefaultSslEngineFactory implements SslEngineFactory {

private final SSLContext context;
private final String[] cipherSuites;

/** Builds a new instance from the driver configuration. */
public DefaultSslEngineFactory(DriverConfigProfile config) {
try {
this.context = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cannot initialize SSL Context", e);
}
if (config.isDefined(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES)) {
List<String> list = config.getStringList(CoreDriverOption.SSL_CONFIG_CIPHER_SUITES);
String tmp[] = new String[list.size()];
this.cipherSuites = list.toArray(tmp);
} else {
this.cipherSuites = null;
}
}

@Override
public SSLEngine newSslEngine(SocketAddress remoteEndpoint) {
SSLEngine engine;
if (remoteEndpoint instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) remoteEndpoint;
engine = context.createSSLEngine(address.getHostName(), address.getPort());
} else {
engine = context.createSSLEngine();
}
engine.setUseClientMode(true);
if (cipherSuites != null) {
engine.setEnabledCipherSuites(cipherSuites);
}
return engine;
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2017-2017 DataStax Inc.
*
* Licensed 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 com.datastax.oss.driver.api.core.ssl;

import java.net.SocketAddress;
import javax.net.ssl.SSLEngine;

/**
* Extension point to configure SSL based on the built-in JDK implementation.
*
* <p>Note that, for advanced use cases (such as bypassing the JDK in favor of another SSL
* implementation), the driver's internal API provides a lower-level interface: {@link
* com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory}.
*/
public interface SslEngineFactory {
/**
* Creates a new SSL engine each time a connection is established.
*
* @param remoteEndpoint the remote endpoint we are connecting to (the address of the Cassandra
* node).
*/
SSLEngine newSslEngine(SocketAddress remoteEndpoint);
}
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2017-2017 DataStax Inc.
*
* Licensed 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.
*/
/** Support for secured communication between the driver and Cassandra nodes. */
package com.datastax.oss.driver.api.core.ssl;
Expand Up @@ -18,11 +18,13 @@
import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.config.CoreDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer;
import com.datastax.oss.driver.internal.core.channel.WriteCoalescer;
import com.datastax.oss.driver.internal.core.config.typesafe.TypeSafeDriverConfig;
import com.datastax.oss.driver.internal.core.protocol.ByteBufPrimitiveCodec;
import com.datastax.oss.driver.internal.core.ssl.JdkSslHandlerFactory;
import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory;
import com.datastax.oss.driver.internal.core.util.Reflection;
import com.datastax.oss.driver.internal.core.util.concurrent.LazyReference;
import com.datastax.oss.protocol.internal.Compressor;
Expand Down Expand Up @@ -52,6 +54,8 @@ public class DefaultDriverContext implements DriverContext {
new LazyReference<>("authProvider", this::buildAuthProvider);
private final LazyReference<WriteCoalescer> writeCoalescerRef =
new LazyReference<>("writeCoalescer", this::buildWriteCoalescer);
private final LazyReference<SslHandlerFactory> sslHandlerFactoryRef =
new LazyReference<>("sslHandlerFactory", this::buildSslHandlerFactory);

private DriverConfig buildDriverConfig() {
return new TypeSafeDriverConfig(
Expand All @@ -77,15 +81,25 @@ protected NettyOptions buildNettyOptions() {
}

protected AuthProvider buildAuthProvider() {
DriverConfigProfile defaultConfig = config().defaultProfile();
CoreDriverOption classOption = CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS;
if (defaultConfig.isDefined(classOption)) {
String className = defaultConfig.getString(classOption);
return Reflection.buildWithConfig(
className, AuthProvider.class, defaultConfig, classOption.getPath());
} else {
return AuthProvider.NONE;
}
AuthProvider configuredProvider =
Reflection.buildFromConfig(
config().defaultProfile(),
CoreDriverOption.AUTHENTICATION_PROVIDER_CLASS,
AuthProvider.class);
return (configuredProvider == null) ? AuthProvider.NONE : configuredProvider;
}

protected SslHandlerFactory buildSslHandlerFactory() {
// If a JDK-based factory was provided through the public API, wrap that, otherwise no SSL.
SslEngineFactory sslEngineFactory =
Reflection.buildFromConfig(
config().defaultProfile(), CoreDriverOption.SSL_FACTORY_CLASS, SslEngineFactory.class);
return (sslEngineFactory == null)
? SslHandlerFactory.NONE
: new JdkSslHandlerFactory(sslEngineFactory);

// For more advanced options (like using Netty's native OpenSSL support instead of the JDK),
// extend DefaultDriverContext and override this method
}

private WriteCoalescer buildWriteCoalescer() {
Expand Down Expand Up @@ -126,4 +140,9 @@ public AuthProvider authProvider() {
public WriteCoalescer writeCoalescer() {
return writeCoalescerRef.get();
}

@Override
public SslHandlerFactory sslHandlerFactory() {
return sslHandlerFactoryRef.get();
}
}
Expand Up @@ -18,6 +18,7 @@
import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.internal.core.channel.WriteCoalescer;
import com.datastax.oss.driver.internal.core.ssl.SslHandlerFactory;
import com.datastax.oss.protocol.internal.Compressor;
import com.datastax.oss.protocol.internal.FrameCodec;
import io.netty.buffer.ByteBuf;
Expand Down Expand Up @@ -50,4 +51,6 @@ public interface DriverContext {
AuthProvider authProvider();

WriteCoalescer writeCoalescer();

SslHandlerFactory sslHandlerFactory();
}
Expand Up @@ -30,6 +30,7 @@
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import java.net.SocketAddress;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -97,7 +98,7 @@ private void connect(
.group(nettyOptions.ioEventLoopGroup())
.channel(nettyOptions.channelClass())
.option(ChannelOption.ALLOCATOR, nettyOptions.allocator())
.handler(initializer(currentVersion, keyspace));
.handler(initializer(address, currentVersion, keyspace));

nettyOptions.afterBootstrapInitialized(bootstrap);

Expand Down Expand Up @@ -142,7 +143,7 @@ private void connect(

@VisibleForTesting
ChannelInitializer<Channel> initializer(
final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) {
SocketAddress address, final ProtocolVersion protocolVersion, final CqlIdentifier keyspace) {
return new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
Expand All @@ -164,8 +165,13 @@ protected void initChannel(Channel channel) throws Exception {
setKeyspaceTimeoutMillis);
ProtocolInitHandler initHandler =
new ProtocolInitHandler(driverContext, protocolVersion, clusterName, keyspace);
channel
.pipeline()

ChannelPipeline pipeline = channel.pipeline();
driverContext
.sslHandlerFactory()
.newSslHandler(channel, address)
.map(h -> pipeline.addLast("ssl", h));
pipeline
.addLast("encoder", new FrameEncoder(driverContext.frameCodec()))
.addLast("decoder", new FrameDecoder(driverContext.frameCodec(), maxFrameLength))
.addLast("inflight", inFlightHandler)
Expand Down
Expand Up @@ -18,6 +18,7 @@
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.typesafe.config.Config;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class TypesafeDriverConfigProfile implements DriverConfigProfile {
Expand Down Expand Up @@ -47,6 +48,11 @@ public String getString(DriverOption option) {
return config.getString(option.getPath());
}

@Override
public List<String> getStringList(DriverOption option) {
return config.getStringList(option.getPath());
}

@Override
public long getBytes(DriverOption option) {
return config.getBytes(option.getPath());
Expand Down
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2017-2017 DataStax Inc.
*
* Licensed 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 com.datastax.oss.driver.internal.core.ssl;

import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
import io.netty.channel.Channel;
import io.netty.handler.ssl.SslHandler;
import java.net.SocketAddress;
import java.util.Optional;
import javax.net.ssl.SSLEngine;

/** SSL handler factory used when JDK-based SSL was configured through the driver's public API. */
public class JdkSslHandlerFactory implements SslHandlerFactory {
private final SslEngineFactory sslEngineFactory;

public JdkSslHandlerFactory(SslEngineFactory sslEngineFactory) {
this.sslEngineFactory = sslEngineFactory;
}

@Override
public Optional<SslHandler> newSslHandler(Channel channel, SocketAddress remoteEndpoint) {
SSLEngine engine = sslEngineFactory.newSslEngine(remoteEndpoint);
return Optional.of(new SslHandler(engine));
}
}

0 comments on commit b8394cc

Please sign in to comment.