diff --git a/CHANGES.txt b/CHANGES.txt index 34c1154950..9b84f503c4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Provide keystore_password_file and truststore_password_file options to read credentials from a file (CASSANDRA-13428) * Unregistering a node should also remove it from tokenMap if it is there and recalculate the placements (CASSANDRA-20346) * Fix PartitionUpdate.isEmpty deserialization issue to avoid potential EOFException (CASSANDRA-20345) * Avoid adding LEFT nodes to tokenMap on upgrade from gossip (CASSANDRA-20344) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 9f8f24b584..6eb150f5df 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1723,6 +1723,10 @@ server_encryption_options: # Set to a valid keystore if internode_encryption is dc, rack or all keystore: conf/.keystore #keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + #keystore_password_file: conf/keystore_passwordfile.txt # Configure the way Cassandra creates SSL contexts. # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory # ssl_context_factory: @@ -1734,11 +1738,20 @@ server_encryption_options: # to create SSLContext. By default, outbound_keystore is the same as keystore indicating mTLS is not enabled. # outbound_keystore: conf/.keystore # outbound_keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When outbound_keystore_password and outbound_keystore_password_file both are specified, + # the outbound_keystore_password will take precedence + # The password in the file should be on the first line + #outbound_keystore_password_file: conf/outbound_keystore_passwordfile.txt # Verify peer server certificates require_client_auth: false # Set to a valid trustore if require_client_auth is true truststore: conf/.truststore #truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + #truststore_password_file: conf/truststore_passwordfile.txt # Verify that the host name in the certificate matches the connected host require_endpoint_verification: false # More advanced defaults: @@ -1780,6 +1793,10 @@ client_encryption_options: # Set keystore and keystore_password to valid keystores if enabled is true keystore: conf/.keystore #keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + #keystore_password_file: conf/keystore_passwordfile.txt # Configure the way Cassandra creates SSL contexts. # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory # ssl_context_factory: @@ -1794,6 +1811,10 @@ client_encryption_options: # Set trustore and truststore_password if require_client_auth is true # truststore: conf/.truststore # truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + #truststore_password_file: conf/truststore_passwordfile.txt # More advanced defaults: # protocol: TLS # store_type: JKS @@ -1844,8 +1865,16 @@ client_encryption_options: # accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1] # keystore: conf/cassandra_ssl.keystore # keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + # keystore_password_file: conf/keystore_passwordfile.txt # truststore: conf/cassandra_ssl.truststore # truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + # truststore_password_file: conf/truststore_passwordfile.txt # # jmx authentication and authorization options. # authenticate: false diff --git a/conf/cassandra_latest.yaml b/conf/cassandra_latest.yaml index f8d791b26f..9c86beeea8 100644 --- a/conf/cassandra_latest.yaml +++ b/conf/cassandra_latest.yaml @@ -1613,6 +1613,10 @@ server_encryption_options: # Set to a valid keystore if internode_encryption is dc, rack or all keystore: conf/.keystore #keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + #keystore_password_file: conf/keystore_passwordfile.txt # Configure the way Cassandra creates SSL contexts. # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory # ssl_context_factory: @@ -1624,11 +1628,20 @@ server_encryption_options: # to create SSLContext. By default, outbound_keystore is the same as keystore indicating mTLS is not enabled. # outbound_keystore: conf/.keystore # outbound_keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When outbound_keystore_password and outbound_keystore_password_file both are specified, + # the outbound_keystore_password will take precedence + # The password in the file should be on the first line + #outbound_keystore_password_file: conf/outbound_keystore_passwordfile.txt # Verify peer server certificates require_client_auth: false # Set to a valid trustore if require_client_auth is true truststore: conf/.truststore #truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + #truststore_password_file: conf/truststore_passwordfile.txt # Verify that the host name in the certificate matches the connected host require_endpoint_verification: false # More advanced defaults: @@ -1663,6 +1676,10 @@ client_encryption_options: # Set keystore and keystore_password to valid keystores if enabled is true keystore: conf/.keystore #keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + #keystore_password_file: conf/keystore_passwordfile.txt # Configure the way Cassandra creates SSL contexts. # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory # ssl_context_factory: @@ -1677,6 +1694,10 @@ client_encryption_options: # Set trustore and truststore_password if require_client_auth is true # truststore: conf/.truststore # truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + #truststore_password_file: conf/truststore_passwordfile.txt # More advanced defaults: # protocol: TLS # store_type: JKS @@ -1718,8 +1739,16 @@ client_encryption_options: # accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1] # keystore: conf/cassandra_ssl.keystore # keystore_password: cassandra + # Optional configuration to specify password for keystore in a separate file + # When keystore_password and keystore_password_file both are specified, the keystore_password will take precedence + # The password in the file should be on the first line + # keystore_password_file: conf/keystore_passwordfile.txt # truststore: conf/cassandra_ssl.truststore # truststore_password: cassandra + # Optional configuration to specify password for truststore in a separate file + # When truststore_password and truststore_password_file both are specified, the truststore_password will take precedence + # The password in the file should be on the first line + # truststore_password_file: conf/truststore_passwordfile.txt # # jmx authentication and authorization options. # authenticate: false diff --git a/doc/modules/cassandra/pages/managing/operating/security.adoc b/doc/modules/cassandra/pages/managing/operating/security.adoc index e7748e93fb..cdc76a625d 100644 --- a/doc/modules/cassandra/pages/managing/operating/security.adoc +++ b/doc/modules/cassandra/pages/managing/operating/security.adoc @@ -100,6 +100,28 @@ YAML!) -----END CERTIFICATE----- ---- +* Configuration: PEM private key's password specified via a file + +[source,yaml] +---- + client/server_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + parameters: + private_key: | + -----BEGIN ENCRYPTED PRIVATE KEY----- OR -----BEGIN PRIVATE KEY----- + + -----END ENCRYPTED PRIVATE KEY----- OR -----END PRIVATE KEY----- + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + trusted_certificates: | + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + keystore_password_file: "" +---- + * Configuration: PEM keys/certs defined in files [source,yaml] @@ -112,6 +134,18 @@ YAML!) truststore: ---- +* Configuration: PEM private key's password specified via a file + +[source,yaml] +---- + client/server_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + keystore: + keystore_password_file: "" + truststore: +---- + == SSL Certificate Hot Reloading Beginning with Cassandra 4, Cassandra supports hot reloading of SSL @@ -590,11 +624,27 @@ jmx_encryption_options: truststore_password: cassandra ---- +Below is an example of configuring JMX SSL in `cassandra.yaml` with password files for keystore and truststore. +[source,yaml] +---- +jmx_encryption_options: + enabled: true + cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] + accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1] + keystore: test/conf/cassandra_ssl_test.keystore + keystore_password_file: test/conf/keystore_passwordfile.txt + truststore: test/conf/cassandra_ssl_test.truststore + truststore_password: cassandra + truststore_password_file: test/conf/truststore_passwordfile.txt +---- + Similar to `client/server_encryption_options`, you can specify PEM-based key material or customize the SSL configuration using `ssl_context_factory` in `jmx_encryption_options`. -Below is an example of configuring PEM based key material, +Below is an example of configuring PEM based key material. You can use `keystore_password_file` +configuration with in-line PEM as documented priorly in case you have stored the password for the +keystore in a file. [source,yaml] ---- jmx_encryption_options: diff --git a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java index ebeac6c4e8..12622d4458 100644 --- a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java +++ b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java @@ -150,10 +150,12 @@ public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFacto public KubernetesSecretsSslContextFactory() { keystoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.KEYSTORE.toString(), KEYSTORE_PATH_VALUE), - getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_KEYSTORE_PASSWORD)); + getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_KEYSTORE_PASSWORD), + null); trustStoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString(), TRUSTSTORE_PATH_VALUE), - getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_TRUSTSTORE_PASSWORD)); + getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_TRUSTSTORE_PASSWORD), + null); keystoreLastUpdatedTime = System.nanoTime(); keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH, @@ -168,11 +170,13 @@ public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFacto super(parameters); keystoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.KEYSTORE.toString(), KEYSTORE_PATH_VALUE), getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR, - KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD)); + KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD), + null); trustStoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString(), TRUSTSTORE_PATH_VALUE), getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR, - TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD)); + TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD), + null); keystoreLastUpdatedTime = System.nanoTime(); keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH, KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE); diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java index 1cd3b7addd..07f78b9a5e 100644 --- a/src/java/org/apache/cassandra/config/EncryptionOptions.java +++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java @@ -114,9 +114,13 @@ public class EncryptionOptions public final String keystore; @Nullable public final String keystore_password; + @Nullable + public final String keystore_password_file; public final String truststore; @Nullable public final String truststore_password; + @Nullable + public final String truststore_password_file; public final List cipher_suites; protected String protocol; protected List accepted_protocols; @@ -153,10 +157,13 @@ public class EncryptionOptions { KEYSTORE("keystore"), KEYSTORE_PASSWORD("keystore_password"), + KEYSTORE_PASSWORD_FILE("keystore_password_file"), OUTBOUND_KEYSTORE("outbound_keystore"), OUTBOUND_KEYSTORE_PASSWORD("outbound_keystore_password"), + OUTBOUND_KEYSTORE_PASSWORD_FILE("outbound_keystore_password_file"), TRUSTSTORE("truststore"), TRUSTSTORE_PASSWORD("truststore_password"), + TRUSTSTORE_PASSWORD_FILE("truststore_password_file"), CIPHER_SUITES("cipher_suites"), PROTOCOL("protocol"), ACCEPTED_PROTOCOLS("accepted_protocols"), @@ -199,8 +206,10 @@ public class EncryptionOptions new HashMap<>()); keystore = "conf/.keystore"; keystore_password = null; + keystore_password_file = null; truststore = "conf/.truststore"; truststore_password = null; + truststore_password_file = null; cipher_suites = null; protocol = null; accepted_protocols = null; @@ -214,18 +223,22 @@ public class EncryptionOptions certificate_validity_warn_threshold = null; } - public EncryptionOptions(ParameterizedClass ssl_context_factory, String keystore, String keystore_password, - String truststore, String truststore_password, List cipher_suites, - String protocol, List accepted_protocols, String algorithm, String store_type, - String require_client_auth, boolean require_endpoint_verification, Boolean enabled, - Boolean optional, DurationSpec.IntMinutesBound max_certificate_validity_period, + public EncryptionOptions(ParameterizedClass ssl_context_factory, + String keystore, String keystore_password, String keystore_password_file, + String truststore, String truststore_password, String truststore_password_file, + List cipher_suites, String protocol, List accepted_protocols, + String algorithm, String store_type, String require_client_auth, + boolean require_endpoint_verification, Boolean enabled, Boolean optional, + DurationSpec.IntMinutesBound max_certificate_validity_period, DurationSpec.IntMinutesBound certificate_validity_warn_threshold) { this.ssl_context_factory = ssl_context_factory; this.keystore = keystore; this.keystore_password = keystore_password; + this.keystore_password_file = keystore_password_file; this.truststore = truststore; this.truststore_password = truststore_password; + this.truststore_password_file = truststore_password_file; this.cipher_suites = cipher_suites; this.protocol = protocol; this.accepted_protocols = accepted_protocols; @@ -244,8 +257,10 @@ public class EncryptionOptions ssl_context_factory = options.ssl_context_factory; keystore = options.keystore; keystore_password = options.keystore_password; + keystore_password_file = options.keystore_password_file; truststore = options.truststore; truststore_password = options.truststore_password; + truststore_password_file = options.truststore_password_file; cipher_suites = options.cipher_suites; protocol = options.protocol; accepted_protocols = options.accepted_protocols; @@ -324,8 +339,10 @@ public class EncryptionOptions */ putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.KEYSTORE, this.keystore); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.KEYSTORE_PASSWORD, this.keystore_password); + putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.KEYSTORE_PASSWORD_FILE, this.keystore_password_file); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.TRUSTSTORE, this.truststore); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.TRUSTSTORE_PASSWORD, this.truststore_password); + putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.TRUSTSTORE_PASSWORD_FILE, this.truststore_password_file); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.CIPHER_SUITES, this.cipher_suites); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.PROTOCOL, this.protocol); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.ACCEPTED_PROTOCOLS, this.accepted_protocols); @@ -502,56 +519,72 @@ public class EncryptionOptions public EncryptionOptions withSslContextFactory(ParameterizedClass sslContextFactoryClass) { - return new EncryptionOptions(sslContextFactoryClass, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(sslContextFactoryClass, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withKeyStore(String keystore) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withKeyStorePassword(String keystore_password) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, + store_type, require_client_auth, require_endpoint_verification, enabled, + optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); + } + + public EncryptionOptions withKeyStorePasswordFile(String keystore_password_file) + { + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withTrustStore(String truststore) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withTrustStorePassword(String truststore_password) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, + store_type, require_client_auth, require_endpoint_verification, enabled, + optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); + } + + public EncryptionOptions withTrustStorePasswordFile(String truststore_password_file) + { + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withCipherSuites(List cipher_suites) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withCipherSuites(String... cipher_suites) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, ImmutableList.copyOf(cipher_suites), protocol, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, ImmutableList.copyOf(cipher_suites), protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); @@ -559,8 +592,8 @@ public class EncryptionOptions public EncryptionOptions withProtocol(String protocol) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } @@ -568,8 +601,8 @@ public class EncryptionOptions public EncryptionOptions withAcceptedProtocols(List accepted_protocols) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols == null ? null : + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols), algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); @@ -578,64 +611,64 @@ public class EncryptionOptions public EncryptionOptions withAlgorithm(String algorithm) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withStoreType(String store_type) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withRequireClientAuth(ClientAuth require_client_auth) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth.value, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withEnabled(boolean enabled) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withOptional(Boolean optional) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, max_certificate_validity_period).applyConfig(); } public EncryptionOptions withMaxCertificateValidityPeriod(DurationSpec.IntMinutesBound maxCertificateValidityPeriod) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, maxCertificateValidityPeriod, certificate_validity_warn_threshold).applyConfig(); } public EncryptionOptions withCertificateValidityWarnThreshold(DurationSpec.IntMinutesBound certificateValidityWarnThreshold) { - return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, algorithm, + return new EncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, truststore, + truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional, max_certificate_validity_period, certificateValidityWarnThreshold).applyConfig(); } @@ -659,8 +692,10 @@ public class EncryptionOptions require_endpoint_verification == opt.require_endpoint_verification && Objects.equals(keystore, opt.keystore) && Objects.equals(keystore_password, opt.keystore_password) && + Objects.equals(keystore_password_file, opt.keystore_password_file) && Objects.equals(truststore, opt.truststore) && Objects.equals(truststore_password, opt.truststore_password) && + Objects.equals(truststore_password_file, opt.truststore_password_file) && Objects.equals(protocol, opt.protocol) && Objects.equals(accepted_protocols, opt.accepted_protocols) && Objects.equals(algorithm, opt.algorithm) && @@ -679,8 +714,10 @@ public class EncryptionOptions int result = 0; result += 31 * (keystore == null ? 0 : keystore.hashCode()); result += 31 * (keystore_password == null ? 0 : keystore_password.hashCode()); + result += 31 * (keystore_password_file == null ? 0 : keystore_password_file.hashCode()); result += 31 * (truststore == null ? 0 : truststore.hashCode()); result += 31 * (truststore_password == null ? 0 : truststore_password.hashCode()); + result += 31 * (truststore_password_file == null ? 0 : truststore_password_file.hashCode()); result += 31 * (protocol == null ? 0 : protocol.hashCode()); result += 31 * (accepted_protocols == null ? 0 : accepted_protocols.hashCode()); result += 31 * (algorithm == null ? 0 : algorithm.hashCode()); @@ -707,6 +744,8 @@ public class EncryptionOptions public final String outbound_keystore; @Nullable public final String outbound_keystore_password; + @Nullable + public final String outbound_keystore_password_file; public ServerEncryptionOptions() { @@ -714,11 +753,13 @@ public class EncryptionOptions this.legacy_ssl_storage_port_enabled = false; this.outbound_keystore = null; this.outbound_keystore_password = null; + this.outbound_keystore_password_file = null; } public ServerEncryptionOptions(ParameterizedClass sslContextFactoryClass, String keystore, - String keystore_password,String outbound_keystore, - String outbound_keystore_password, String truststore, String truststore_password, + String keystore_password, String keystore_password_file, String outbound_keystore, + String outbound_keystore_password, String outbound_keystore_password_file, + String truststore, String truststore_password, String truststore_password_file, List cipher_suites, String protocol, List accepted_protocols, String algorithm, String store_type, String require_client_auth, boolean require_endpoint_verification, Boolean optional, @@ -726,13 +767,15 @@ public class EncryptionOptions DurationSpec.IntMinutesBound maxCertificateAgeMinutes, DurationSpec.IntMinutesBound certificateValidityWarnThreshold) { - super(sslContextFactoryClass, keystore, keystore_password, truststore, truststore_password, cipher_suites, + super(sslContextFactoryClass, keystore, keystore_password, keystore_password_file, + truststore, truststore_password, truststore_password_file, cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, null, optional, maxCertificateAgeMinutes, certificateValidityWarnThreshold); this.internode_encryption = internode_encryption; this.legacy_ssl_storage_port_enabled = legacy_ssl_storage_port_enabled; this.outbound_keystore = outbound_keystore; this.outbound_keystore_password = outbound_keystore_password; + this.outbound_keystore_password_file = outbound_keystore_password_file; } public ServerEncryptionOptions(ServerEncryptionOptions options) @@ -742,6 +785,7 @@ public class EncryptionOptions this.legacy_ssl_storage_port_enabled = options.legacy_ssl_storage_port_enabled; this.outbound_keystore = options.outbound_keystore; this.outbound_keystore_password = options.outbound_keystore_password; + this.outbound_keystore_password_file = options.outbound_keystore_password_file; } @Override @@ -750,6 +794,7 @@ public class EncryptionOptions super.fillSslContextParams(sslContextFactoryParameters); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.OUTBOUND_KEYSTORE, this.outbound_keystore); putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.OUTBOUND_KEYSTORE_PASSWORD, this.outbound_keystore_password); + putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.OUTBOUND_KEYSTORE_PASSWORD_FILE, this.outbound_keystore_password_file); } @Override @@ -845,7 +890,8 @@ public class EncryptionOptions return internode_encryption == opt.internode_encryption && legacy_ssl_storage_port_enabled == opt.legacy_ssl_storage_port_enabled && Objects.equals(outbound_keystore, opt.outbound_keystore) && - Objects.equals(outbound_keystore_password, opt.outbound_keystore_password); + Objects.equals(outbound_keystore_password, opt.outbound_keystore_password) && + Objects.equals(outbound_keystore_password_file, opt.outbound_keystore_password_file); } /** @@ -861,15 +907,17 @@ public class EncryptionOptions result += 31 * Boolean.hashCode(legacy_ssl_storage_port_enabled); result += 31 * (outbound_keystore == null ? 0 : outbound_keystore.hashCode()); result += 31 * (outbound_keystore_password == null ? 0 : outbound_keystore_password.hashCode()); + result += 31 * (outbound_keystore_password_file == null ? 0 : outbound_keystore_password_file.hashCode()); return result; } @Override public ServerEncryptionOptions withSslContextFactory(ParameterizedClass sslContextFactoryClass) { - return new ServerEncryptionOptions(sslContextFactoryClass, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(sslContextFactoryClass, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -879,9 +927,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withKeyStore(String keystore) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -891,9 +940,23 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withKeyStorePassword(String keystore_password) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, + algorithm, store_type, require_client_auth, + require_endpoint_verification, optional, internode_encryption, + legacy_ssl_storage_port_enabled, max_certificate_validity_period, + max_certificate_validity_period).applyConfigInternal(); + } + + @Override + public ServerEncryptionOptions withKeyStorePasswordFile(String keystore_password_file) + { + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -903,9 +966,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withTrustStore(String truststore) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -915,9 +979,23 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withTrustStorePassword(String truststore_password) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, + algorithm, store_type, require_client_auth, + require_endpoint_verification, optional, internode_encryption, + legacy_ssl_storage_port_enabled, max_certificate_validity_period, + max_certificate_validity_period).applyConfigInternal(); + } + + @Override + public ServerEncryptionOptions withTrustStorePasswordFile(String truststore_password_file) + { + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -927,9 +1005,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withCipherSuites(List cipher_suites) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -939,10 +1018,11 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withCipherSuites(String... cipher_suites) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, Arrays.asList(cipher_suites), protocol, - accepted_protocols, algorithm, store_type, require_client_auth, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + Arrays.asList(cipher_suites), protocol, accepted_protocols, + algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, max_certificate_validity_period).applyConfigInternal(); @@ -951,9 +1031,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withProtocol(String protocol) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -963,9 +1044,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withAcceptedProtocols(List accepted_protocols) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -975,9 +1057,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withAlgorithm(String algorithm) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -987,9 +1070,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withStoreType(String store_type) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -999,9 +1083,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withRequireClientAuth(ClientAuth require_client_auth) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth.value, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -1011,9 +1096,10 @@ public class EncryptionOptions @Override public ServerEncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -1022,9 +1108,10 @@ public class EncryptionOptions public ServerEncryptionOptions withOptional(boolean optional) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -1033,9 +1120,10 @@ public class EncryptionOptions public ServerEncryptionOptions withInternodeEncryption(InternodeEncryption internode_encryption) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -1044,9 +1132,10 @@ public class EncryptionOptions public ServerEncryptionOptions withLegacySslStoragePort(boolean enable_legacy_ssl_storage_port) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, enable_legacy_ssl_storage_port, max_certificate_validity_period, @@ -1055,9 +1144,10 @@ public class EncryptionOptions public ServerEncryptionOptions withOutboundKeystore(String outboundKeystore) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outboundKeystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outboundKeystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, @@ -1066,21 +1156,34 @@ public class EncryptionOptions public ServerEncryptionOptions withOutboundKeystorePassword(String outboundKeystorePassword) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outboundKeystorePassword, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outboundKeystorePassword, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, max_certificate_validity_period, max_certificate_validity_period).applyConfigInternal(); } + public ServerEncryptionOptions withOutboundKeystorePasswordFile(String outboundKeystorePasswordFile) + { + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outboundKeystorePasswordFile, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, + algorithm, store_type, require_client_auth, + require_endpoint_verification, optional, internode_encryption, + legacy_ssl_storage_port_enabled, max_certificate_validity_period, + max_certificate_validity_period).applyConfigInternal(); + } @Override public ServerEncryptionOptions withMaxCertificateValidityPeriod(DurationSpec.IntMinutesBound maxCertificateValidityPeriod) { - return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, - outbound_keystore, outbound_keystore_password, truststore, - truststore_password, cipher_suites, protocol, accepted_protocols, + return new ServerEncryptionOptions(ssl_context_factory, keystore, keystore_password, keystore_password_file, + outbound_keystore, outbound_keystore_password, outbound_keystore_password_file, + truststore, truststore_password, truststore_password_file, + cipher_suites, protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification, optional, internode_encryption, legacy_ssl_storage_port_enabled, maxCertificateValidityPeriod, diff --git a/src/java/org/apache/cassandra/config/JMXServerOptions.java b/src/java/org/apache/cassandra/config/JMXServerOptions.java index 8b51d3b756..705ab02bb8 100644 --- a/src/java/org/apache/cassandra/config/JMXServerOptions.java +++ b/src/java/org/apache/cassandra/config/JMXServerOptions.java @@ -194,11 +194,17 @@ public class JMXServerOptions boolean sslEnabled = COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean(); + // CASSANDRA-13428: Support for specifying password file for keystores is only added to the `encryption_options` + // in the `cassandra.yaml`. Since the JMX SSL Config can also leverage it as per CASSANDRA-18508, password file + // support is not added to the JMX SSL configuration via the system properties. Hence, `null` is used as + // the password file arguments for the keystore and the truststore while constructing the encryption options here. EncryptionOptions encryptionOptions = new EncryptionOptions(new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory", new HashMap<>()), keystore, keystorePassword, + null, truststore, truststorePassword, + null, cipherSuites, null, // protocol acceptedProtocols, diff --git a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java index 39e584d257..e0867493d7 100644 --- a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java +++ b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java @@ -36,9 +36,13 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.io.util.File; +import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.Clock; +import static java.lang.String.format; + /** * Abstract implementation for {@link ISslContextFactory} using file based, standard keystore format with the ability * to hot-reload the files upon file changes (detected by the {@code last modified timestamp}). @@ -60,18 +64,19 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto public FileBasedSslContextFactory() { - keystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra"); - outboundKeystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra"); - trustStoreContext = new FileBasedStoreContext("conf/.truststore", "cassandra"); + keystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra", null); + outboundKeystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra", null); + trustStoreContext = new FileBasedStoreContext("conf/.truststore", "cassandra", null); } public FileBasedSslContextFactory(Map parameters) { super(parameters); - keystoreContext = new FileBasedStoreContext(getString("keystore"), getString("keystore_password")); + keystoreContext = new FileBasedStoreContext(getString("keystore"), getString("keystore_password"), getString("keystore_password_file")); outboundKeystoreContext = new FileBasedStoreContext(StringUtils.defaultString(getString("outbound_keystore"), keystoreContext.filePath), - StringUtils.defaultString(getString("outbound_keystore_password"), keystoreContext.password)); - trustStoreContext = new FileBasedStoreContext(getString("truststore"), getString("truststore_password")); + StringUtils.defaultString(getString("outbound_keystore_password"), keystoreContext.password), + StringUtils.defaultString(getString("outbound_keystore_password_file"), keystoreContext.passwordFilePath)); + trustStoreContext = new FileBasedStoreContext(getString("truststore"), getString("truststore_password"), getString("truststore_password_file")); } @Override @@ -135,7 +140,7 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto if (password == null) { String keyName = isOutboundKeystore ? "outbound_" : ""; - final String msg = String.format("'%skeystore_password' must be specified", keyName); + final String msg = format("'%skeystore_password' must be specified", keyName); throw new IllegalArgumentException(msg); } } @@ -278,11 +283,13 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto public volatile boolean checkedExpiry = false; public String filePath; public String password; + public String passwordFilePath; - public FileBasedStoreContext(String keystore, String keystorePassword) + public FileBasedStoreContext(String keystoreFilePath, String keystorePassword, String keystorePasswordFilePath) { - this.filePath = keystore; - this.password = keystorePassword; + this.filePath = keystoreFilePath; + this.passwordFilePath = keystorePasswordFilePath; + this.password = resolvePassword(keystoreFilePath, keystorePassword, keystorePasswordFilePath); } protected boolean hasKeystore() @@ -294,5 +301,38 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto { return StringUtils.isEmpty(password) || keyPassword.equals(password); } + + private static String resolvePassword(String keystoreFilePath, String password, String passwordFilePath) + { + if (password != null) + return password; + + if (StringUtils.isEmpty(passwordFilePath)) + return null; + + File keystorePasswordFile = new File(passwordFilePath); + + if (!keystorePasswordFile.exists()) + { + final String msg = format("keystore password file %s does not exist", keystorePasswordFile.path()); + throw new ConfigurationException(msg); + } + + try + { + // we expect a password to be on the first line + List lines = FileUtils.readLines(keystorePasswordFile); + if (lines.isEmpty()) + return ""; + + return lines.get(0); + } + catch (RuntimeException e) + { + throw new ConfigurationException(format("'Failed to read keystore password from the %s for %s", + keystorePasswordFile, keystoreFilePath), + e); + } + } } } diff --git a/test/conf/cassandra-jmx-sslconfig-with-passwordfile.yaml b/test/conf/cassandra-jmx-sslconfig-with-passwordfile.yaml new file mode 100644 index 0000000000..0b495485d1 --- /dev/null +++ b/test/conf/cassandra-jmx-sslconfig-with-passwordfile.yaml @@ -0,0 +1,74 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# +# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing +# implementation classes. +# +cluster_name: Test Cluster +# memtable_allocation_type: heap_buffers +memtable_allocation_type: offheap_objects +commitlog_sync: batch +commitlog_segment_size: 5MiB +commitlog_directory: build/test/cassandra/commitlog +# commitlog_compression: +# - class_name: LZ4Compressor +cdc_raw_directory: build/test/cassandra/cdc_raw +cdc_enabled: false +hints_directory: build/test/cassandra/hints +partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner +listen_address: 127.0.0.1 +storage_port: 7012 +ssl_storage_port: 17012 +start_native_transport: true +native_transport_port: 9042 +column_index_size: 4KiB +saved_caches_directory: build/test/cassandra/saved_caches +data_file_directories: + - build/test/cassandra/data +disk_access_mode: mmap_index_only +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1:7012" +endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch +dynamic_snitch: true +jmx_server_options: + enabled: true + jmx_encryption_options: + enabled: true + cipher_suites: [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] + accepted_protocols: [TLSv1.2,TLSv1.3,TLSv1.1] + keystore: test/conf/cassandra_ssl_test.keystore + keystore_password_file: test/conf/cassandra_ssl_test_keystore_passwordfile.txt + truststore: test/conf/cassandra_ssl_test.truststore + truststore_password_file: test/conf/cassandra_ssl_test_truststore_passwordfile.txt +incremental_backups: true +concurrent_compactors: 4 +compaction_throughput: 0MiB/s +row_cache_class_name: org.apache.cassandra.cache.OHCProvider +row_cache_size: 16MiB +user_defined_functions_enabled: true +scripted_user_defined_functions_enabled: false +prepared_statements_cache_size: 1MiB +corrupted_tombstone_strategy: exception +stream_entire_sstables: true +stream_throughput_outbound: 24MiB/s +sasi_indexes_enabled: true +materialized_views_enabled: true +file_cache_enabled: true diff --git a/test/conf/cassandra-pem-sslcontextfactory-with-passwordfile.yaml b/test/conf/cassandra-pem-sslcontextfactory-with-passwordfile.yaml new file mode 100644 index 0000000000..19ef0f3cd7 --- /dev/null +++ b/test/conf/cassandra-pem-sslcontextfactory-with-passwordfile.yaml @@ -0,0 +1,152 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +# +# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing +# implementation classes. +# +cluster_name: Test Cluster +# memtable_allocation_type: heap_buffers +memtable_allocation_type: offheap_objects +commitlog_sync: periodic +commitlog_sync_period: 10s +commitlog_segment_size: 5MiB +commitlog_directory: build/test/cassandra/commitlog +# commitlog_compression: +# - class_name: LZ4Compressor +cdc_raw_directory: build/test/cassandra/cdc_raw +cdc_enabled: false +hints_directory: build/test/cassandra/hints +partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner +listen_address: 127.0.0.1 +storage_port: 7012 +ssl_storage_port: 17012 +start_native_transport: true +native_transport_port: 9042 +column_index_size: 4KiB +saved_caches_directory: build/test/cassandra/saved_caches +data_file_directories: + - build/test/cassandra/data +disk_access_mode: mmap_index_only +seed_provider: + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + - seeds: "127.0.0.1:7012" +endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch +dynamic_snitch: true +client_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + parameters: + private_key: | + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/ + g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl + xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29 + L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V + sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/ + f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8 + RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR + 0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs + evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU + tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6 + wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN + K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv + zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5 + mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo + WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ + jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6 + eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny + DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn + AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY + Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow + Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut + ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr + bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH + hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI + RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw + pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP + FujZhqbKUDbYAcqTkoQ= + -----END ENCRYPTED PRIVATE KEY----- + -----BEGIN CERTIFICATE----- + MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV + bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD + VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh + Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx + EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu + a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw + FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d + ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy + q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2 + TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto + TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA + YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD + N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v + iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh + IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv + 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG + qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa + HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru + n3MVF9w= + -----END CERTIFICATE----- + trusted_certificates: | + -----BEGIN CERTIFICATE----- + MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV + bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD + VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh + Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx + EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu + a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw + FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d + ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy + q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2 + TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto + TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA + YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD + N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v + iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh + IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv + 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG + qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa + HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru + n3MVF9w= + -----END CERTIFICATE----- + keystore_password_file: test/conf/cassandra_ssl_test_keystore_passwordfile.txt +server_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + internode_encryption: none + keystore: test/conf/cassandra_ssl_test.keystore.pem + keystore_password_file: test/conf/cassandra_ssl_test_keystore_passwordfile.txt + truststore: test/conf/cassandra_ssl_test.truststore.pem +incremental_backups: true +concurrent_compactors: 4 +compaction_throughput: 0MiB/s +row_cache_class_name: org.apache.cassandra.cache.OHCProvider +row_cache_size: 16MiB +user_defined_functions_enabled: true +scripted_user_defined_functions_enabled: false +prepared_statements_cache_size: 1MiB +corrupted_tombstone_strategy: exception +stream_entire_sstables: true +stream_throughput_outbound: 24MiB/s +sasi_indexes_enabled: true +materialized_views_enabled: true +file_cache_enabled: true diff --git a/test/conf/cassandra_ssl_test_keystore_passwordfile.txt b/test/conf/cassandra_ssl_test_keystore_passwordfile.txt new file mode 100644 index 0000000000..03ef327b7a --- /dev/null +++ b/test/conf/cassandra_ssl_test_keystore_passwordfile.txt @@ -0,0 +1 @@ +cassandra \ No newline at end of file diff --git a/test/conf/cassandra_ssl_test_outbound_keystore_password.txt b/test/conf/cassandra_ssl_test_outbound_keystore_password.txt new file mode 100644 index 0000000000..03ef327b7a --- /dev/null +++ b/test/conf/cassandra_ssl_test_outbound_keystore_password.txt @@ -0,0 +1 @@ +cassandra \ No newline at end of file diff --git a/test/conf/cassandra_ssl_test_truststore_passwordfile.txt b/test/conf/cassandra_ssl_test_truststore_passwordfile.txt new file mode 100644 index 0000000000..03ef327b7a --- /dev/null +++ b/test/conf/cassandra_ssl_test_truststore_passwordfile.txt @@ -0,0 +1 @@ +cassandra \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/config/EncryptionOptionsEqualityTest.java b/test/unit/org/apache/cassandra/config/EncryptionOptionsEqualityTest.java index 317c8ec626..5e6d26b817 100644 --- a/test/unit/org/apache/cassandra/config/EncryptionOptionsEqualityTest.java +++ b/test/unit/org/apache/cassandra/config/EncryptionOptionsEqualityTest.java @@ -81,6 +81,54 @@ public class EncryptionOptionsEqualityTest assertEquals(encryptionOptions1.hashCode(), encryptionOptions2.hashCode()); } + @Test + public void testKeystoreOptionsWithPasswordFile() { + EncryptionOptions encryptionOptions1 = + new EncryptionOptions() + .withStoreType("JKS") + .withKeyStore(TlsTestUtils.SERVER_KEYSTORE_PATH) + .withKeyStorePasswordFile(TlsTestUtils.SERVER_KEYSTORE_PASSWORD_FILE) + .withTrustStore(TlsTestUtils.SERVER_TRUSTSTORE_PATH) + .withTrustStorePasswordFile(TlsTestUtils.SERVER_TRUSTSTORE_PASSWORD_FILE) + .withProtocol("TLSv1.1") + .withRequireClientAuth(REQUIRED) + .withRequireEndpointVerification(false); + + EncryptionOptions encryptionOptions2 = + new EncryptionOptions() + .withStoreType("JKS") + .withKeyStore(TlsTestUtils.SERVER_KEYSTORE_PATH) + .withKeyStorePasswordFile(TlsTestUtils.SERVER_KEYSTORE_PASSWORD_FILE) + .withTrustStore(TlsTestUtils.SERVER_TRUSTSTORE_PATH) + .withTrustStorePasswordFile(TlsTestUtils.SERVER_TRUSTSTORE_PASSWORD_FILE) + .withProtocol("TLSv1.1") + .withRequireClientAuth(REQUIRED) + .withRequireEndpointVerification(false); + + assertEquals(encryptionOptions1, encryptionOptions2); + assertEquals(encryptionOptions1.hashCode(), encryptionOptions2.hashCode()); + } + + @Test + public void testMismatchForKeystoreOptionsWithPasswordFile() + { + EncryptionOptions.ServerEncryptionOptions encryptionOptions1 = createServerEncryptionOptions(); + EncryptionOptions.ServerEncryptionOptions encryptionOptions2 = createServerEncryptionOptions(); + + encryptionOptions1 = encryptionOptions1 + .withKeyStore(TlsTestUtils.SERVER_KEYSTORE_PATH) + .withKeyStorePassword(null) + .withKeyStorePasswordFile(TlsTestUtils.SERVER_KEYSTORE_PASSWORD_FILE); + + encryptionOptions2 = encryptionOptions2 + .withKeyStore(TlsTestUtils.SERVER_KEYSTORE_PATH) + .withKeyStorePassword(null) + .withKeyStorePasswordFile(TlsTestUtils.SERVER_TRUSTSTORE_PASSWORD_FILE); + + assertNotEquals(encryptionOptions1, encryptionOptions2); + assertNotEquals(encryptionOptions1.hashCode(), encryptionOptions2.hashCode()); + } + @Test public void testSameCustomSslContextFactoryImplementation() { diff --git a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java index a295095dbb..5ef08eb060 100644 --- a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java +++ b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java @@ -61,8 +61,8 @@ public class EncryptionOptionsTest { return new EncryptionOptionsTestCase(new EncryptionOptions(new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory", new HashMap<>()), - keystorePath, "dummypass", - "dummytruststore", "dummypass", + keystorePath, "dummypass", null, + "dummytruststore", "dummypass", null, Collections.emptyList(), null, null, null, "JKS", "false", false, enabled, optional, null, null) .applyConfig(), expected, @@ -75,8 +75,8 @@ public class EncryptionOptionsTest { return new EncryptionOptionsTestCase(new EncryptionOptions(new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory", customSslContextFactoryParams), - keystorePath, "dummypass", - "dummytruststore", "dummypass", + keystorePath, "dummypass", null, + "dummytruststore", "dummypass", null, Collections.emptyList(), null, null, null, "JKS", "false", false, enabled, optional, null, null) .applyConfig(), expected, @@ -127,7 +127,10 @@ public class EncryptionOptionsTest EncryptionOptions.TlsEncryptionPolicy expected) { return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory", - new HashMap<>()), keystorePath, "dummypass", keystorePath, "dummypass", "dummytruststore", "dummypass", + new HashMap<>()), + keystorePath, "dummypass", null, + keystorePath, "dummypass", null, + "dummytruststore", "dummypass", null, Collections.emptyList(), null, null, null, "JKS", "false", false, optional, internodeEncryption, false, null, null) .applyConfig(), expected, diff --git a/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java index 2916f0d460..d6d936ba0e 100644 --- a/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.distributed.shared.WithProperties; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.transport.TlsTestUtils; import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG; @@ -89,7 +90,7 @@ public class FileBasedSslContextFactoryTest } /** - * Tests that empty {@code keystore_password} and {@code outbound_keystore_password} is allowed. + * Tests that empty {@code keystore_password} and {@code outbound_keystore_password} are allowed. */ @Test public void testEmptyKeystorePasswords() throws SSLException @@ -103,7 +104,7 @@ public class FileBasedSslContextFactoryTest Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", localEncryptionOptions.ssl_context_factory.class_name); Assert.assertEquals("keystore_password must be empty", "", localEncryptionOptions.keystore_password); - Assert.assertEquals("outbound_keystore_password must empty", "", localEncryptionOptions.outbound_keystore_password); + Assert.assertEquals("outbound_keystore_password must be empty", "", localEncryptionOptions.outbound_keystore_password); TestFileBasedSSLContextFactory sslContextFactory = (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; @@ -112,6 +113,46 @@ public class FileBasedSslContextFactoryTest sslContextFactory.buildTrustManagerFactory(); } + @Test + public void testKeystorePasswordFile() throws SSLException + { + // Here we only override password configuration and specify password_file configuration since keystore paths + // are already loaded in the `encryptionOptions` + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions + .withKeyStorePassword(null) + .withKeyStorePasswordFile(TlsTestUtils.SERVER_KEYSTORE_PASSWORD_FILE) + .withOutboundKeystorePassword(null) + .withOutboundKeystorePasswordFile(TlsTestUtils.SERVER_OUTBOUND_KEYSTORE_PASSWORD_FILE) + .withTrustStorePassword(null) + .withTrustStorePasswordFile(TlsTestUtils.SERVER_TRUSTSTORE_PASSWORD_FILE); + + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + + /** + * Tests for missing password configuration and non-existance file specified in the password_file configuration. + * @throws SSLException + */ + @Test(expected = ConfigurationException.class) + public void testBadKeystorePasswordFile() throws SSLException + { + // Here we only override password configuration and specify password_file configuration since keystore paths + // are already loaded in the `encryptionOptions` + encryptionOptions + .withKeyStorePassword(null) + .withKeyStorePasswordFile("/path/to/non-existance-password-file") + .withOutboundKeystorePassword(null) + .withOutboundKeystorePasswordFile("/path/to/non-existance-password-file") + .withTrustStorePassword(null) + .withTrustStorePasswordFile("/path/to/non-existance-password-file"); + } + /** * Tests that an absent keystore_password for the {@code keystore} is disallowed. */ diff --git a/test/unit/org/apache/cassandra/security/FileBasedStoreContextTest.java b/test/unit/org/apache/cassandra/security/FileBasedStoreContextTest.java new file mode 100644 index 0000000000..cd1e52c61f --- /dev/null +++ b/test/unit/org/apache/cassandra/security/FileBasedStoreContextTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.security; + +import org.junit.Test; + +import org.apache.cassandra.exceptions.ConfigurationException; + +import static org.junit.Assert.assertEquals; + +public class FileBasedStoreContextTest +{ + /** + * Tests default behavior without keystore password file specified. + */ + @Test + public void testPasswordConfiguration() + { + FileBasedSslContextFactory.FileBasedStoreContext wrapper = new FileBasedSslContextFactory.FileBasedStoreContext("test/conf/cassandra_ssl_test.keystore", "cassandra", null); + assertEquals("Password must be loaded from the direct configuration", "cassandra", wrapper.password); + } + + /** + * Tests behavior when password for keystore is specified via a password file. + */ + @Test + public void testPasswordFileConfiguration() + { + FileBasedSslContextFactory.FileBasedStoreContext wrapper = new FileBasedSslContextFactory.FileBasedStoreContext("test/conf/cassandra_ssl_test.keystore", null, "test/conf/cassandra_ssl_test_keystore_passwordfile.txt"); + assertEquals("Password must be loaded from the password file", "cassandra", wrapper.password); + } + + /** + * Tests when password for keystore is specified via password configuration and a password file both. + */ + @Test + public void testPasswordAndPasswordFileConfiguration() + { + String expectedPassword = "cassandra123"; + FileBasedSslContextFactory.FileBasedStoreContext wrapper = new FileBasedSslContextFactory.FileBasedStoreContext("test/conf/cassandra_ssl_test.keystore", expectedPassword, "test/conf/cassandra_ssl_test_keystore_passwordfile.txt"); + assertEquals("Password configuration must take precedence", expectedPassword, wrapper.password); + } + + /** + * Tests behavior when a non-existing password file is specified for keystore's password and password configuration + * is {@code null}. + */ + @Test(expected = ConfigurationException.class) + public void testMissingPasswordFile() + { + new FileBasedSslContextFactory.FileBasedStoreContext("test/conf/cassandra_ssl_test.keystore", null, "passwordfile-that-doesnotexist"); + } + + /** + * Tests behavior when non-null empty password is specified in the password configuration. + * The empty password via the configuration must take precedence in this case. + */ + @Test + public void testBlankPasswordConfiguration() + { + FileBasedSslContextFactory.FileBasedStoreContext wrapper = new FileBasedSslContextFactory.FileBasedStoreContext("test/conf/cassandra_ssl_test.keystore", "", "test/conf/cassandra_ssl_test_keystore_passwordfile.txt"); + assertEquals("Password must be loaded from the direct configuration", "", wrapper.password); + } +} diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithPasswordFileTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithPasswordFileTest.java new file mode 100644 index 0000000000..fc837a3729 --- /dev/null +++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithPasswordFileTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.security; + +import javax.net.ssl.SSLException; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.distributed.shared.WithProperties; + +import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG; +import static org.junit.Assert.assertEquals; + +public class PEMBasedSslContextFactoryConfigWithPasswordFileTest +{ + static WithProperties properties; + + @BeforeClass + public static void setupDatabaseDescriptor() + { + properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra-pem-sslcontextfactory-with-passwordfile.yaml"); + } + + @AfterClass + public static void tearDownDatabaseDescriptor() + { + properties.close(); + } + + @Test + public void testHappyPathInlinePEM() throws SSLException + { + + Config config = DatabaseDescriptor.loadConfig(); + config.client_encryption_options.applyConfig(); + + assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory", + config.client_encryption_options.ssl_context_factory.class_name); + assertEquals(config.client_encryption_options.ssl_context_factory.class_name, + config.client_encryption_options.sslContextFactoryInstance.getClass().getName()); + PEMBasedSslContextFactory sslContextFactory = + (PEMBasedSslContextFactory) config.client_encryption_options.sslContextFactoryInstance; + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + + @Test + public void testHappyPathFileBasedPEM() throws SSLException + { + + Config config = DatabaseDescriptor.loadConfig(); + config.server_encryption_options.applyConfig(); + + assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory", + config.server_encryption_options.ssl_context_factory.class_name); + assertEquals(config.server_encryption_options.ssl_context_factory.class_name, + config.server_encryption_options.sslContextFactoryInstance.getClass().getName()); + PEMBasedSslContextFactory sslContextFactory = + (PEMBasedSslContextFactory) config.server_encryption_options.sslContextFactoryInstance; + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } +} diff --git a/test/unit/org/apache/cassandra/transport/TlsTestUtils.java b/test/unit/org/apache/cassandra/transport/TlsTestUtils.java index f3994e7613..30a6054127 100644 --- a/test/unit/org/apache/cassandra/transport/TlsTestUtils.java +++ b/test/unit/org/apache/cassandra/transport/TlsTestUtils.java @@ -58,16 +58,19 @@ public class TlsTestUtils public static String SERVER_KEYSTORE_PATH_PEM = "test/conf/cassandra_ssl_test.keystore.pem"; public static String SERVER_KEYSTORE_PATH_UNENCRYPTED_PEM = "test/conf/cassandra_ssl_test.unencrypted_keystore.pem"; public static String SERVER_KEYSTORE_PASSWORD = "cassandra"; + public static String SERVER_KEYSTORE_PASSWORD_FILE = "test/conf/cassandra_ssl_test_keystore_passwordfile.txt"; public static String SERVER_KEYSTORE_ENDPOINT_VERIFY_PATH = "test/conf/cassandra_ssl_test_endpoint_verify.keystore"; public static String SERVER_KEYSTORE_ENDPOINT_VERIFY_PASSWORD = "cassandra"; public static String SERVER_OUTBOUND_KEYSTORE_PATH = "test/conf/cassandra_ssl_test_outbound.keystore"; public static String SERVER_OUTBOUND_KEYSTORE_PASSWORD = "cassandra"; + public static String SERVER_OUTBOUND_KEYSTORE_PASSWORD_FILE = "test/conf/cassandra_ssl_test_outbound_keystore_password.txt"; public static String SERVER_TRUSTSTORE_PATH = "test/conf/cassandra_ssl_test.truststore"; public static String SERVER_TRUSTSTORE_PEM_PATH = "test/conf/cassandra_ssl_test.truststore.pem"; public static String SERVER_TRUSTSTORE_PASSWORD = "cassandra"; + public static String SERVER_TRUSTSTORE_PASSWORD_FILE = "test/conf/cassandra_ssl_test_truststore_passwordfile.txt"; // To regenerate: // 1. generate keystore diff --git a/test/unit/org/apache/cassandra/utils/jmx/JMXSslConfiguredWithYamlFileOptionsAndPasswordFileTest.java b/test/unit/org/apache/cassandra/utils/jmx/JMXSslConfiguredWithYamlFileOptionsAndPasswordFileTest.java new file mode 100644 index 0000000000..ae2b7e6128 --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/jmx/JMXSslConfiguredWithYamlFileOptionsAndPasswordFileTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.utils.jmx; + +import java.net.InetAddress; +import java.util.Map; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.net.ssl.SSLException; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import org.apache.commons.lang3.StringUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.JMXServerOptions; +import org.apache.cassandra.distributed.shared.WithProperties; +import org.apache.cassandra.utils.JMXServerUtils; + +import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_CONFIG; +import static org.apache.cassandra.config.CassandraRelevantProperties.COM_SUN_MANAGEMENT_JMXREMOTE_SSL; +import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES; +import static org.apache.cassandra.config.CassandraRelevantProperties.JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for JMX SSL configuration specified in the cassandra.yaml using jmx_encryption_options with password files for + * keystore and truststores as applicable. + * @see JMXSslConfiguredWithYamlFileOptionsTest + */ +public class JMXSslConfiguredWithYamlFileOptionsAndPasswordFileTest +{ + static WithProperties properties; + + @BeforeClass + public static void setupDatabaseDescriptor() + { + properties = new WithProperties().set(CASSANDRA_CONFIG, "cassandra-jmx-sslconfig-with-passwordfile.yaml"); + DatabaseDescriptor.daemonInitialization(); + } + + @AfterClass + public static void tearDownDatabaseDescriptor() + { + properties.close(); + } + + @Test + public void testYamlFileJmxEncryptionOptions() throws SSLException + { + JMXServerOptions serverOptions = DatabaseDescriptor.getJmxServerOptions(); + String expectedProtocols = StringUtils.join(serverOptions.jmx_encryption_options.getAcceptedProtocols(), ","); + String expectedCipherSuites = StringUtils.join(serverOptions.jmx_encryption_options.cipherSuitesArray(), ","); + + InetAddress serverAddress = InetAddress.getLoopbackAddress(); + + try (WithProperties ignored = JMXSslPropertiesUtil.use(false)) + { + Map env = JMXServerUtils.configureJmxSocketFactories(serverAddress, serverOptions); + assertTrue("com.sun.management.jmxremote.ssl must be true", COM_SUN_MANAGEMENT_JMXREMOTE_SSL.getBoolean()); + assertNotNull("ServerSocketFactory must not be null", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE)); + assertTrue("RMI_SERVER_SOCKET_FACTORY must be of JMXSslRMIServerSocketFactory type", env.get(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) instanceof SslRMIServerSocketFactory); + assertNotNull("ClientSocketFactory must not be null", env.get(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE)); + assertNotNull("com.sun.jndi.rmi.factory.socket must be set in the env", env.get("com.sun.jndi.rmi.factory.socket")); + assertEquals("javax.rmi.ssl.client.enabledProtocols must match", expectedProtocols, JAVAX_RMI_SSL_CLIENT_ENABLED_PROTOCOLS.getString()); + assertEquals("javax.rmi.ssl.client.enabledCipherSuites must match", expectedCipherSuites, JAVAX_RMI_SSL_CLIENT_ENABLED_CIPHER_SUITES.getString()); + } + } +}