This example shows how to connect to a Cassandra cluster using SSL encryption (client to node encryption). This is ideally a continuation of the post from The Last Pickle blog that explains how to implement node-to-node encryption in a Cassandra cluster. There instructions do not cover the client authentication that requires a slight different configuration. I'll will use the same passwords, file names, identifiers and conventions of that post in the following. In particular I will use the Cassandra Cluster Manager (CCM) to provide with a working cluster.
-
Follow the cited blog post to enable internode encryption. If you want, you can skip the internode part, however you will need the
ca-cert
file and provide a keyStore file for you node as specified. -
Generate a trustStore named
client-truststore.jks
including the CA root certificate runningkeytool -keystore client-truststore.jks \ -alias CARoot \ -importcert -file ca-cert \ -keypass mypass \ -storepass truststorepass \ -noprompt`
-
Put
client-truststore.jks
underresources
folder of this project. This way client is shares your Certification Authority with any node in the cluster. This will allow encrypted communication after the handshake. -
Change the client_encryption_options section of
cassandra-yaml
for every node of the cluster as follows to enable client encryption (but not authentication):client_encryption_options: enabled: true keystore: /Users/Giampaolo/.ccm/sslverify/$NODE/conf/server-keystore.jks keystore_password: awesomekeypass optional: false
-
Run
ClientToNode
classmain
method to verify that the setup is correct.
Inside main
method, trustStore file and password are set programmatically, using getCluster(String trustStoreLocation, String trustStorePassword, String host)
method
cluster = getCluster("/client-truststore.jks", "truststorepass", host);
session = cluster.newSession();
where the method is defined as follows:
// This method is an example of loading a truststore from a resource, decoding it with its password.
private Cluster getCluster(String trustStoreLocation, String trustStorePassword, String host) {
Cluster cluster;
SSLContext sslcontext = null;
try {
InputStream is = ClientToNode.class.getResourceAsStream(trustStoreLocation);
KeyStore keystore = KeyStore.getInstance("jks");
char[] pwd = trustStorePassword.toCharArray();
keystore.load(is, pwd);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
TrustManager[] tm = tmf.getTrustManagers();
sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, tm, null);
} catch (KeyStoreException kse) {
logger.error(kse.getMessage(), kse);
} catch (CertificateException e) {
logger.error(e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
logger.error(e.getMessage(), e);
} catch (KeyManagementException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
JdkSSLOptions sslOptions = JdkSSLOptions.builder()
.withSSLContext(sslcontext)
.build();
cluster = Cluster.builder()
.addContactPoint(host)
.withSSL(sslOptions)
.build();
return cluster;
}
However you can simplify code if you pass trustStore and related password through command line using
-Djavax.net.ssl.trustStore=/Users/Giampaolo/dev/cassandra-ssl-client-to-node-example/target/classes/client-truststore.jks
-Djavax.net.ssl.trustStorePassword=truststorepass
In this case, you neeed to build the cluster only with the SSL option enabled:
Cluster cluster = Cluster.builder()
.addContactPoint(host)
.withSSL()
.build();
If you want to invoke ClientToNode
class without the help of an IDE, you can run the following commands in the root folder of the project:
mvn package appassembler:assemble
,sh target/appassembler/bin/clientToNode 127.0.0.1
,
SSL parameters are specified in pom.xml
at extraJvmArguments
tag.
To enable debug SSL run with -Djavax.net.debug=ssl
. Don't forget to apply changes to all nodes and restart the cluster.
If you allow client encryption, cqlsh
won't work anymore as is. You need to follow these steps to connect to cluster.
-
Run
keytool -importkeystore \ -srckeystore node1-server-keystore.jks \ -destkeystore node1.p12 \ -deststoretype PKCS12 \ -alias node1 \ -srcstorepass awesomepass \ -srckeypass awesomepass \ -deststorepass clientpassword \ -destkeypass clientpassword
to convert the server certificate to the PKCS12 format.
-
Convert the
PKCS#12
to the PEM format with this commandopenssl pkcs12 -in node1.p12 -out node1.pem -nodes
. The option-nodes
is not the word "nodes" so it's unrelated to Cassandra nodes, but rather is "no DES" which means that OpenSSL will not encrypt the private key in aPKCS#12
file. -
Move the file
node1.pem
to the folder~/.cassandra
. This folder holds the command history forcqlsh
,cli
andnodetool
session. It contains also the.cqlshrs
file that specifies different settings for the cqlsh sessions. The.cassandra
directory holds command history for yourcqlsh
,cli
, andnodetool
sessions. It is also the default location for the.cqlshrc
file, which allows you to persist various settings for yourcqlsh
sessions. -
Edit or create the
~/.cassandra/cqlshrc
file adding the following sections if not present:[authentication] username = password = [connection] hostname = localhost port = 9042 factory = cqlshlib.ssl.ssl_transport_factory [ssl] certfile = /Users/Giampaolo/.cassandra/node1.pem validate = true ## Optional, true by default.
-
Connect with
ccm node1 cqlsh --ssl
.cqlsh
allows you to pass the file path also as theSSL_CERTFILE
variable or modifying thecqlshrc
file like this
In alternative, you can specify certificate for every node in the [certfiles]
section of
[authentication]
username =
password =
[connection]
hostname = localhost
port = 9042
factory = cqlshlib.ssl.ssl_transport_factory
[ssl]
validate = true ## Optional, true by default.
[certfiles]
127.0.0.1 = /Users/Giampaolo/.cassandra/node1.pem
127.0.0.2 = /Users/Giampaolo/.cassandra/node1.pem
127.0.0.3 = /Users/Giampaolo/.cassandra/node1.pem