Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cassandra SSL with own Certificate Authority

I want to setup my own CA for use with a cassandra cluster so that I do not have to copy all of the certificates around every time I add a new node. I have read a few tutorials for Cassandra and SSL but they all work with copying certificates around. I am a little lost in the CA process

This is what I think I need to do

  • Create CA once
  • Create a CSR per node and then sign each with my CA (-> save as nodeX.crt)
  • import the node0.crt to my cassandra node0 keystore, node1.crt to node1 keystore etc

Now:

  • Do I need to add anything to the truststore?
  • Do I need to do anything with the CA file? Copy it to every client / node server?
  • What file do I need to provide my java client with? the cqlsh client?

Advantage: No more copying of ssl certificates between nodes. Just one for each node and you're set.

edit:

Ok this is how I did it. If I made any mistakes, please let me know. I left out things like JCE files and proper cassandra.yaml config. These need to be present on server!

openssl genrsa -out clusterCA.key 2048
openssl req -x509 -new -key clusterCA.key -days <DAYS> -out clusterCA.pem

keytool -importcert -alias clusterCA -file clusterCA.pem -keystore clustertruststore -storepass <PASS>

#on each cassandra host for clients. for client replace nodename with clientname
keytool -genkeypair -alias <NODENAME> -keyalg RSA -keysize 2048 -dname "CN=<NODENAME>,OU=<UNITNAME>,O=<ORGANISATION>" -keypass <PASS> -keystore <NODENAME>.keystore -storepass <PASS> -validity <DAYS>

keytool -keystore <NODENAME>.keystore -alias <NODENAME> -certreq -file <NODENAME>.cert -storepass <PASS> -keypass <PASS>


# sign it with CA

openssl x509 -req -CA clusterCA.pem -CAkey clusterCa.key -in <NODENAME>.cert -out <NODENAME>.signed -days <DAYS> -CAcreateserial

# add rootCA to host

keytool -keystore <NODENAME>.keystore -storepass <PASS> -alias clusterCA -import -file clusterCA.pem -noprompt

keytool -keystore <NODENAME>.keystore -storepass <PASS> -alias <NODENAME> -import -file <NODENAME>.signed -keypass <PASS>

## use <NODENAME>.keystore as truststore and keystore for cassandra node / client trust/keystore
## No need to copy keystores around. You only need it on your host


## create CQLSH pem
keytool -importkeystore -srckeystore <NODENAME>.keystore -destkeystore <NODENAME>_user1.p12 -deststoretype PKCS12
openssl pkcs12 -in <NODENAME>_user1.p12 -out <NODENAME>_user1.pem -nodes

##  use <NODENAME>_user1.pem as certfile for cqlsh
like image 682
elmalto Avatar asked May 27 '15 17:05

elmalto


1 Answers

Your strategy is very sound and that is the way I'd do it. You want to have your own Certificate Authority and then create a CSR for each node. That's much easier to manage than trusting node certificates individually.

  • Each node will have it's own keystore storing it's cert.
  • You will want every node to have the CAs public certificate in its truststore. This is only if you have 'require_client_auth' set to true. I'd recommend doing this as it isn't too difficult to set up and adds an extra layer of identification which should be considered important.

It is also important to differentiate between internode encryption and client encryption. Cassandra has different settings for each (documented in the links above). If using client-to-node encryption you will also want to have a truststore for client certificates. You could use the same trust store and also issue certificates to clients.

On the client-to-node side here's an example from the java-driver tests how to set up your SSLContext using your key and truststores:

/**
 * @param keyStorePath Path to keystore, if absent is not used.
 * @param trustStorePath Path to truststore, if absent is not used.
 * @return {@link com.datastax.driver.core.SSLOptions} with the given keystore and truststore path's for
 * server certificate validation and client certificate authentication.
 */
public SSLOptions getSSLOptions(Optional<String> keyStorePath, Optional<String> trustStorePath) throws Exception {

    TrustManagerFactory tmf = null;
    if(trustStorePath.isPresent()) {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(this.getClass().getResourceAsStream(trustStorePath.get()), DEFAULT_CLIENT_TRUSTSTORE_PASSWORD.toCharArray());

        tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
    }

    KeyManagerFactory kmf = null;
    if(keyStorePath.isPresent()) {
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(this.getClass().getResourceAsStream(keyStorePath.get()), DEFAULT_CLIENT_KEYSTORE_PASSWORD.toCharArray());

        kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, DEFAULT_CLIENT_KEYSTORE_PASSWORD.toCharArray());
    }

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf != null ? kmf.getKeyManagers() : null, tmf != null ? tmf.getTrustManagers() : null, new SecureRandom());

    return new SSLOptions(sslContext, SSLOptions.DEFAULT_SSL_CIPHER_SUITES);
}

Once you an SSLOptions object you can simply pass it into your Cluster Builder, i.e.:

cluster = Cluster.builder()
    .addContactPoint(host)
    .withSSL(sslOptions))
    .build();

CQLSH supports SSL via the cqlshrc file. You can find an example of how to set that up here.

like image 161
Andy Tolbert Avatar answered Oct 01 '22 02:10

Andy Tolbert