You can secure traffic between the driver and Cassandra with SSL. There are two aspects to that:
- client-to-node encryption, where the traffic is encrypted, and the client verifies the identity of the Cassandra nodes it connects to;
- optionally, client certificate authentication, where Cassandra nodes also verify the identity of the client.
This section describes the driver-side configuration; it assumes that you've already configured SSL in Cassandra:
- the Cassandra documentation covers a basic approach with self-signed certificates, which is fine for development and tests.
- this blog post details a more advanced solution based on a Certificate Authority (CA).
This is required for client-to-node encryption.
If you're using self-signed certificates, you need to export the public part of each node's certificate from that node's keystore:
keytool -export -alias cassandra -file cassandranode0.cer -keystore .keystore
Then add all public certificates to the client truststore:
keytool -import -v -trustcacerts -alias <cassandra_node0> -file cassandranode0.cer -keystore client.truststore
keytool -import -v -trustcacerts -alias <cassandra_node1> -file cassandranode1.cer -keystore client.truststore
...
If you're using a Certificate Authority, the client truststore only needs to contain the CA's certificate:
keytool -import -v -trustcacerts -alias CARoot -file ca.cer -keystore client.truststore
If you also intend to use client certificate authentication, generate the public and private key pair for the client:
keytool -genkey -keyalg RSA -alias client -keystore client.keystore
If you're using self-signed certificates, extract the public part of the client certificate, and import it in the truststore of each Cassandra node:
keytool -export -alias client -file client.cer -keystore client.keystore
keytool -import -v -trustcacerts -alias client -file client.cer -keystore server.truststore
If you're using a CA, sign the client certificate with it (see the blog post linked at the top of this page). Then the nodes' truststores only need to contain the CA's certificate (which should already be the case if you've followed the steps for inter-node encryption).
The base class to configure SSL is RemoteEndpointAwareSSLOptions. It's very generic, but you don't necessarily need to deal with it directly: the default instance, or the provided subclasses, might be enough for your needs.
withSSL()
gives you a basic JSSE configuration:
Cluster cluster = Cluster.builder()
.addContactPoint("127.0.0.1")
.withSSL()
.build();
You can then use JSSE system properties for specific details, like keystore locations and passwords:
-Djavax.net.ssl.trustStore=/path/to/client.truststore
-Djavax.net.ssl.trustStorePassword=password123
# If you're using client authentication:
-Djavax.net.ssl.keyStore=/path/to/client.keystore
-Djavax.net.ssl.keyStorePassword=password123
If you need more control than what system properties allow, you can configure SSL programmatically with RemoteEndpointAwareJdkSSLOptions:
SSLContext sslContext = ... // create and configure SSL context
RemoteEndpointAwareJdkSSLOptions sslOptions = RemoteEndpointAwareJdkSSLOptions.builder()
.withSSLContext(context)
.build();
Cluster cluster = Cluster.builder()
.addContactPoint("127.0.0.1")
.withSSL(sslOptions)
.build();
Note that you can also extend the class and override
newSSLEngine(SocketChannel,InetSocketAddress) if you need specific
configuration on the SSLEngine
. For example, to enable hostname verification:
SSLContext sslContext = ... // create and configure SSL context
RemoteEndpointAwareJdkSSLOptions sslOptions = new RemoteEndpointAwareJdkSSLOptions(sslContext, null) {
protected SSLEngine newSSLEngine(SocketChannel channel, InetSocketAddress remoteEndpoint) {
SSLEngine engine = super.newSSLEngine(channel, remoteEndpoint);
SSLParameters parameters = engine.getSSLParameters();
// HTTPS endpoint identification includes hostname verification against certificate's common name.
// This API is only available for JDK7+.
parameters.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(parameters);
return engine;
}
};
Cluster cluster = Cluster.builder()
.addContactPoint("127.0.0.1")
.withSSL(sslOptions)
.build();
RemoteEndpointAwareNettySSLOptions allows you to use Netty's SslContext
instead of
the JDK directly. The advantage is that Netty can use OpenSSL directly,
which provides better performance and generates less garbage. A disadvantage of
using the OpenSSL provider is that it requires platform-specific dependencies,
unlike the JDK provider.
OpenSSL doesn't use keystores, so if you use client authentication and generated your certificates with keytool, you need to convert them.
-
use this command to extract the public certificate chain:
keytool -export -keystore client.keystore -alias client -rfc -file client.crt
-
follow this tutorial to extract your client's private key from
client.keystore
to a text fileclient.key
in PEM format.
Netty-tcnative provides the native integration with OpenSSL. Follow these instructions to add it to your dependencies.
There are known runtime incompatibilities between newer versions of netty-tcnative and the version of netty that the driver uses. For best results, use version 2.0.7.Final.
Using netty-tcnative requires JDK 1.7 or above and requires the presence of OpenSSL on the system. It will not fall back to the JDK implementation.
Use the following Java code to configure OpenSSL with your certificates:
KeyStore ks = KeyStore.getInstance("JKS");
// make sure you close this stream properly (not shown here for brevity)
InputStream trustStore = new FileInputStream("client.truststore");
ks.load(trustStore, "password123".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SslContextBuilder builder = SslContextBuilder
.forClient()
.sslProvider(SslProvider.OPENSSL)
.trustManager(tmf);
// only if you use client authentication
.keyManager(new File("client.crt"), new File("client.key"));
SSLOptions sslOptions = new RemoteEndpointAwareNettySSLOptions(builder.build());
Cluster cluster = Cluster.builder()
.addContactPoint("127.0.0.1")
.withSSL(sslOptions)
.build();