Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Cassandra Encryption Configuration

This section covers the complete encryption configuration options in cassandra.yaml for both internode and client-to-node encryption.

Cassandra provides two separate encryption configurations:

SectionPurposePort
server_encryption_optionsNode-to-node communication7000 (7001 legacy SSL)
client_encryption_optionsClient-to-node communication9042

cassandra.yaml
server_encryption_options:
# Encryption scope
# Options: none, dc, rack, all
internode_encryption: all
# Enable legacy SSL storage port (7001)
# Set to true during migration from unencrypted
# Named enable_legacy_ssl_storage_port in Cassandra 4.0
legacy_ssl_storage_port_enabled: false
# Keystore containing node's private key and certificate
keystore: /etc/cassandra/certs/node-keystore.jks
keystore_password: changeit
# Truststore containing CA certificates
truststore: /etc/cassandra/certs/truststore.jks
truststore_password: changeit
# Require client certificate for internode connections
require_client_auth: true
# Hostname verification (Cassandra 4.0+)
require_endpoint_verification: true
# TLS protocol version
protocol: TLS
# Accepted protocol versions
accepted_protocols:
- TLSv1.2
- TLSv1.3
# Cipher suites (in preference order)
cipher_suites:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
# Algorithm for secure random number generation
# algorithm: SunX509
# Keystore type (JKS, PKCS12, or PEM for Cassandra 4.0+)
# store_type: JKS
# SSL context factory (advanced)
# ssl_context_factory:
# class_name: org.apache.cassandra.security.DefaultSslContextFactory
ModeDescriptionUse Case
noneNo encryptionDevelopment only
dcEncrypt cross-datacenter onlyWAN traffic encryption
rackEncrypt cross-rack and cross-datacenterPhysical rack isolation
allEncrypt all internode trafficRecommended for production

Rolling migration from unencrypted to encrypted internode communication:

Phase 1: Enable Optional Encryption

# On all nodes
server_encryption_options:
internode_encryption: all
legacy_ssl_storage_port_enabled: true
optional: true # Accept both encrypted and unencrypted

Perform rolling restart.

Phase 2: Require Encryption

# On all nodes
server_encryption_options:
internode_encryption: all
legacy_ssl_storage_port_enabled: false
optional: false

Perform rolling restart.

The default value of internode_authenticator is AllowAllInternodeAuthenticator, which accepts every peer connection. In Cassandra 4.0, 4.1 and 5.0, a cluster left on this default accepts a bootstrapping node from any host that can reach the storage port, is configured with the same cluster_name, and has a reachable seed. The joining node claims token ranges and the existing replicas stream it a copy of the data for those ranges. No credential is presented at any point in that exchange.

Client authentication does not authenticate peers

PasswordAuthenticator authenticates client sessions on the native protocol (port 9042) and says nothing about peers connecting on the storage protocol (port 7000). A cluster with client authentication enabled and internode_authenticator left at its default still accepts an anonymous joining node.

Write survey mode extends the exposure further. A node started with -Dcassandra.write_survey=true streams the full data for the ranges it claims, then receives every subsequent write for those ranges while never becoming a read replica and never completing the join, described under write survey mode. The result is a continuously updated copy of the data on a host that never has to serve a request. Such a node is not hidden: peers report it as UJ in nodetool status, and it holds that state for as long as it keeps gossiping, which reads as a stalled bootstrap rather than as an intrusion.

Three controls apply, strongest first:

  • require_client_auth: true in server_encryption_options, with internode_encryption: all. A joining node must then present a certificate signed by a CA in the truststore of the nodes it connects to. The option is available in 4.0, 4.1 and 5.0 and defaults to false. internode_encryption: dc and rack leave connections within the datacenter or rack unencrypted, and a certificate is only checked during a TLS handshake, so those modes leave intra-scope joins unauthenticated. all is required for the check to apply to every connection.
  • MutualTlsInternodeAuthenticator (Cassandra 5.0+, CEP-34, CASSANDRA-18554), which authorizes peers by the identity in the certificate rather than by CA trust alone. It is configured in the subsection below.
  • Restricting the storage port, 7000, or 7001 when the legacy SSL storage port is enabled (legacy_ssl_storage_port_enabled in 4.1 and 5.0, enable_legacy_ssl_storage_port in 4.0), to known cluster members at the network layer. This is defense in depth rather than the primary control, because any host inside the permitted range is still accepted without a certificate.

Internode Authentication with Mutual TLS (Cassandra 5.0+)

Section titled “Internode Authentication with Mutual TLS (Cassandra 5.0+)”

The MutualTlsInternodeAuthenticator provides certificate-based authentication for internode connections by extracting identities from certificates and verifying them against a list of authorized peers.

cassandra.yaml
internode_authenticator:
class_name: org.apache.cassandra.auth.MutualTlsInternodeAuthenticator
parameters:
validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator
# Optional: restrict to specific peer identities
trusted_peer_identities: "spiffe://testdomain.com/cassandra/node1,spiffe://testdomain.com/cassandra/node2"
# Optional: validate outbound keystore identity
node_identity: "spiffe://testdomain.com/cassandra/this-node"
ParameterRequiredDescription
validator_class_nameYesCertificate validator implementation class
trusted_peer_identitiesNoComma-separated list of authorized peer identities
node_identityNoExpected identity from outbound keystore for validation

Prerequisites:

  • server_encryption_options.internode_encryption must be set to all
  • server_encryption_options.require_client_auth must be true

When trusted_peer_identities is configured, only nodes presenting certificates with matching identities can establish internode connections. When omitted, any certificate signed by a trusted CA is accepted.

The node_identity parameter validates the local node's keystore identity at startup, catching configuration errors early.


cassandra.yaml
client_encryption_options:
# Enable client encryption
enabled: true
# Accept unencrypted connections alongside encrypted
# Set to false in production
optional: false
# Keystore containing node's private key and certificate
keystore: /etc/cassandra/certs/node-keystore.jks
keystore_password: changeit
# Truststore for validating client certificates
# Required when require_client_auth is true
truststore: /etc/cassandra/certs/truststore.jks
truststore_password: changeit
# Require client certificates (mutual TLS)
require_client_auth: false
# Hostname verification (Cassandra 4.0+)
require_endpoint_verification: false
# TLS protocol version
protocol: TLS
# Accepted protocol versions
accepted_protocols:
- TLSv1.2
- TLSv1.3
# Cipher suites
cipher_suites:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256

Mutual TLS requires clients to present certificates, providing two-way authentication.

client_encryption_options:
enabled: true
require_client_auth: true
truststore: /etc/cassandra/certs/client-truststore.jks
truststore_password: changeit

With mTLS enabled:

  • Clients must present valid certificates
  • Certificates must be signed by a CA in the truststore
  • Connection fails if client certificate is invalid or missing

Cassandra 4.0+ supports PEM files directly, eliminating JKS/PKCS12 conversion.

server_encryption_options:
internode_encryption: all
keystore: /etc/cassandra/certs/node-key.pem
keystore_password: ""
truststore: /etc/cassandra/certs/ca-chain.pem
truststore_password: ""
client_encryption_options:
enabled: true
keystore: /etc/cassandra/certs/node-key.pem
keystore_password: ""
truststore: /etc/cassandra/certs/ca-cert.pem
truststore_password: ""

Keystore PEM should contain private key followed by certificate chain:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
... (node certificate)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (intermediate CA, if applicable)
-----END CERTIFICATE-----

Truststore PEM should contain CA certificates:

-----BEGIN CERTIFICATE-----
... (root CA)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (intermediate CA, if applicable)
-----END CERTIFICATE-----

Cassandra 4.0+ can reload certificates without restart when files change.

  • Keystore and truststore file paths remain the same
  • New certificates must be valid
  • File modification triggers reload

Check logs for reload messages:

INFO [main] SSLFactory.java:... - Loaded renewed keystore
INFO [main] SSLFactory.java:... - Loaded renewed truststore
Terminal window
# Force SSL context reload
nodetool reloadssl

Terminal window
# Check cassandra.yaml syntax
python3 -c "import yaml; yaml.safe_load(open('/etc/cassandra/cassandra.yaml'))"
# Verify keystore is readable
keytool -list -keystore /etc/cassandra/certs/node-keystore.jks -storepass changeit
# Verify truststore is readable
keytool -list -keystore /etc/cassandra/certs/truststore.jks -storepass changeit
# Verify certificate validity
openssl x509 -in /etc/cassandra/certs/node-cert.pem -noout -dates

After restart, verify TLS is working:

Terminal window
# Test client port
openssl s_client -connect localhost:9042 -tls1_2
# Test internode port
openssl s_client -connect localhost:7000 -tls1_2
# Check negotiated cipher
openssl s_client -connect localhost:9042 2>/dev/null | grep "Cipher is"

server_encryption_options:
internode_encryption: none
client_encryption_options:
enabled: false
server_encryption_options:
internode_encryption: all
keystore: /etc/cassandra/certs/node-keystore.p12
keystore_password: ${KEYSTORE_PASSWORD}
truststore: /etc/cassandra/certs/truststore.p12
truststore_password: ${TRUSTSTORE_PASSWORD}
require_client_auth: true
require_endpoint_verification: true
accepted_protocols:
- TLSv1.2
- TLSv1.3
cipher_suites:
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
client_encryption_options:
enabled: true
optional: false
keystore: /etc/cassandra/certs/node-keystore.p12
keystore_password: ${KEYSTORE_PASSWORD}
truststore: /etc/cassandra/certs/truststore.p12
truststore_password: ${TRUSTSTORE_PASSWORD}
require_client_auth: false
accepted_protocols:
- TLSv1.2
- TLSv1.3
cipher_suites:
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
client_encryption_options:
enabled: true
optional: false
require_client_auth: true
keystore: /etc/cassandra/certs/node-keystore.p12
keystore_password: ${KEYSTORE_PASSWORD}
truststore: /etc/cassandra/certs/client-ca-truststore.p12
truststore_password: ${TRUSTSTORE_PASSWORD}