diff --git a/CHANGES.txt b/CHANGES.txt index 48ad6d5463..49f52402cf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -129,6 +129,7 @@ * Rename DisableFlag class to EnableFlag on guardrails (CASSANDRA-17544) 4.1.2 + * Allow keystore and trustrore passwords to be nullable (CASSANDRA-18124) * Return snapshots with dots in their name in nodetool listsnapshots (CASSANDRA-18371) * Fix NPE when loading snapshots and data directory is one directory from root (CASSANDRA-18359) * Do not submit hints when hinted_handoff_enabled=false (CASSANDRA-18304) diff --git a/NEWS.txt b/NEWS.txt index 848a0fa5ef..27b63c80c0 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -161,6 +161,10 @@ Upgrading `toTimestamp` and `toUnixTimestamp` in Cassandra 2.2. - Hadoop integration is no longer available (CASSANDRA-18323). If you want to process Cassandra data by big data frameworks, please upgrade your infrastructure to use Cassandra Spark connector. + - Keystore/truststore password configurations are nullable now and the code defaults of those passwords to 'cassandra' are + removed. Any deployments that depend upon the code default to this password value without explicitly specifying + it in cassandra.yaml will fail on upgrade. Please specify your keystore_password and truststore_password elements in cassandra.yaml with appropriate + values to prevent this failure. Deprecation ----------- diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index ee616e1674..320fa06fcc 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1361,7 +1361,12 @@ server_encryption_options: legacy_ssl_storage_port_enabled: false # Set to a valid keystore if internode_encryption is dc, rack or all keystore: conf/.keystore - keystore_password: cassandra + #keystore_password: cassandra + # Configure the way Cassandra creates SSL contexts. + # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory + # ssl_context_factory: + # # Must be an instance of org.apache.cassandra.security.ISslContextFactory + # class_name: org.apache.cassandra.security.DefaultSslContextFactory # During internode mTLS authentication, inbound connections (acting as servers) use keystore, keystore_password # containing server certificate to create SSLContext and # outbound connections (acting as clients) use outbound_keystore & outbound_keystore_password with client certificates @@ -1372,7 +1377,7 @@ server_encryption_options: require_client_auth: false # Set to a valid trustore if require_client_auth is true truststore: conf/.truststore - truststore_password: cassandra + #truststore_password: cassandra # Verify that the host name in the certificate matches the connected host require_endpoint_verification: false # More advanced defaults: @@ -1406,7 +1411,12 @@ client_encryption_options: # optional: true # Set keystore and keystore_password to valid keystores if enabled is true keystore: conf/.keystore - keystore_password: cassandra + #keystore_password: cassandra + # Configure the way Cassandra creates SSL contexts. + # To use PEM-based key material, see org.apache.cassandra.security.PEMBasedSslContextFactory + # ssl_context_factory: + # # Must be an instance of org.apache.cassandra.security.ISslContextFactory + # class_name: org.apache.cassandra.security.DefaultSslContextFactory # Verify client certificates require_client_auth: false # require_endpoint_verification: false diff --git a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java index fb11c91e90..18e211b3b8 100644 --- a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java +++ b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java @@ -125,6 +125,8 @@ public class KubernetesSecretsPEMSslContextFactory extends KubernetesSecretsSslC private String pemEncodedCertificates; private PEMBasedSslContextFactory pemBasedSslContextFactory; + public boolean checkedExpiry = false; + public KubernetesSecretsPEMSslContextFactory() { pemBasedSslContextFactory = new PEMBasedSslContextFactory(); @@ -165,7 +167,7 @@ public class KubernetesSecretsPEMSslContextFactory extends KubernetesSecretsSslC protected KeyManagerFactory buildKeyManagerFactory() throws SSLException { KeyManagerFactory kmf = pemBasedSslContextFactory.buildKeyManagerFactory(); - checkedExpiry = pemBasedSslContextFactory.checkedExpiry; + checkedExpiry = pemBasedSslContextFactory.keystoreContext.checkedExpiry; return kmf; } 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 c83fb03296..ebeac6c4e8 100644 --- a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java +++ b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java @@ -149,12 +149,12 @@ public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFacto public KubernetesSecretsSslContextFactory() { - keystore = getString(EncryptionOptions.ConfigKey.KEYSTORE.toString(), KEYSTORE_PATH_VALUE); - keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME, - DEFAULT_KEYSTORE_PASSWORD); - truststore = getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString(), TRUSTSTORE_PATH_VALUE); - truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME, - DEFAULT_TRUSTSTORE_PASSWORD); + keystoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.KEYSTORE.toString(), KEYSTORE_PATH_VALUE), + getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_KEYSTORE_PASSWORD)); + + trustStoreContext = new FileBasedStoreContext(getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString(), TRUSTSTORE_PATH_VALUE), + getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME, DEFAULT_TRUSTSTORE_PASSWORD)); + keystoreLastUpdatedTime = System.nanoTime(); keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH, KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE); @@ -166,12 +166,13 @@ public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFacto public KubernetesSecretsSslContextFactory(Map parameters) { super(parameters); - keystore = getString(EncryptionOptions.ConfigKey.KEYSTORE.toString(), KEYSTORE_PATH_VALUE); - keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR, - KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD); - truststore = getString(EncryptionOptions.ConfigKey.TRUSTSTORE.toString(), TRUSTSTORE_PATH_VALUE); - truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR, - TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD); + 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)); + + 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)); keystoreLastUpdatedTime = System.nanoTime(); keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH, KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE); diff --git a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java index 2d127f32ea..f101e8955d 100644 --- a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java +++ b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java @@ -147,7 +147,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); } @@ -158,7 +158,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest config.putAll(commonConfig); KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); Assert.assertNotNull(trustManagerFactory); } @@ -172,7 +172,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.keystoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildKeyManagerFactory(); } @@ -262,7 +262,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest addKeystoreOptions(config); KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); Assert.assertNotNull(trustManagerFactory); Assert.assertFalse(kubernetesSecretsSslContextFactory.shouldReload()); @@ -282,7 +282,7 @@ public class KubernetesSecretsPEMSslContextFactoryTest addKeystoreOptions(config); KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.keystoreContext.checkedExpiry = false; KeyManagerFactory keyManagerFactory = kubernetesSecretsSslContextFactory.buildKeyManagerFactory(); Assert.assertNotNull(keyManagerFactory); Assert.assertFalse(kubernetesSecretsSslContextFactory.shouldReload()); diff --git a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java index 8b22fff118..0c364196b8 100644 --- a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java +++ b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java @@ -20,8 +20,8 @@ package org.apache.cassandra.security; import java.io.IOException; import java.io.OutputStream; +import java.io.File; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; @@ -38,7 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.EncryptionOptions; -import org.apache.cassandra.io.util.PathUtils; import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR; import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH; @@ -63,11 +62,10 @@ public class KubernetesSecretsSslContextFactoryTest private static void deleteFileIfExists(String file) { - Path filePath = Paths.get(file); - boolean deleted = PathUtils.tryDelete(filePath); + boolean deleted = new File(file).delete(); if (!deleted) { - logger.warn("File {} could not be deleted.", filePath); + logger.warn("File {} could not be deleted.", file); } } @@ -106,7 +104,7 @@ public class KubernetesSecretsSslContextFactoryTest config.put(TRUSTSTORE_PATH, "/this/is/probably/not/a/file/on/your/test/machine"); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); } @@ -119,7 +117,7 @@ public class KubernetesSecretsSslContextFactoryTest config.put(KubernetesSecretsSslContextFactory.DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME, "HomeOfBadPasswords"); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); } @@ -133,7 +131,7 @@ public class KubernetesSecretsSslContextFactoryTest config.put(KubernetesSecretsSslContextFactory.DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME, ""); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); } @@ -144,7 +142,7 @@ public class KubernetesSecretsSslContextFactoryTest config.putAll(commonConfig); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); Assert.assertNotNull(trustManagerFactory); } @@ -155,9 +153,11 @@ public class KubernetesSecretsSslContextFactoryTest Map config = new HashMap<>(); config.putAll(commonConfig); config.put(KEYSTORE_PATH, "/this/is/probably/not/a/file/on/your/test/machine"); + config.put(KEYSTORE_PASSWORD_ENV_VAR, "MY_KEYSTORE_PASSWORD"); + config.put("MY_KEYSTORE_PASSWORD","ThisWontMatter"); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.keystoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildKeyManagerFactory(); } @@ -181,20 +181,20 @@ public class KubernetesSecretsSslContextFactoryTest KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory1 = new KubernetesSecretsSslContextFactoryForTestOnly(config); // Make sure the exiry check didn't happen so far for the private key - Assert.assertFalse(kubernetesSecretsSslContextFactory1.checkedExpiry); + Assert.assertFalse(kubernetesSecretsSslContextFactory1.keystoreContext.checkedExpiry); addKeystoreOptions(config); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory2 = new KubernetesSecretsSslContextFactoryForTestOnly(config); // Trigger the private key loading. That will also check for expired private key kubernetesSecretsSslContextFactory2.buildKeyManagerFactory(); // Now we should have checked the private key's expiry - Assert.assertTrue(kubernetesSecretsSslContextFactory2.checkedExpiry); + Assert.assertTrue(kubernetesSecretsSslContextFactory2.keystoreContext.checkedExpiry); // Make sure that new factory object preforms the fresh private key expiry check KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory3 = new KubernetesSecretsSslContextFactoryForTestOnly(config); - Assert.assertFalse(kubernetesSecretsSslContextFactory3.checkedExpiry); + Assert.assertFalse(kubernetesSecretsSslContextFactory3.keystoreContext.checkedExpiry); kubernetesSecretsSslContextFactory3.buildKeyManagerFactory(); - Assert.assertTrue(kubernetesSecretsSslContextFactory3.checkedExpiry); + Assert.assertTrue(kubernetesSecretsSslContextFactory3.keystoreContext.checkedExpiry); } @Test @@ -205,7 +205,7 @@ public class KubernetesSecretsSslContextFactoryTest addKeystoreOptions(config); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); Assert.assertNotNull(trustManagerFactory); Assert.assertFalse(kubernetesSecretsSslContextFactory.shouldReload()); @@ -225,7 +225,7 @@ public class KubernetesSecretsSslContextFactoryTest addKeystoreOptions(config); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); - kubernetesSecretsSslContextFactory.checkedExpiry = false; + kubernetesSecretsSslContextFactory.keystoreContext.checkedExpiry = false; KeyManagerFactory keyManagerFactory = kubernetesSecretsSslContextFactory.buildKeyManagerFactory(); Assert.assertNotNull(keyManagerFactory); Assert.assertFalse(kubernetesSecretsSslContextFactory.shouldReload()); diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java index 0ab653f088..afa0d66bac 100644 --- a/src/java/org/apache/cassandra/config/EncryptionOptions.java +++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import javax.annotation.Nullable; + import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; @@ -72,8 +74,10 @@ public class EncryptionOptions */ public final ParameterizedClass ssl_context_factory; public final String keystore; + @Nullable public final String keystore_password; public final String truststore; + @Nullable public final String truststore_password; public final List cipher_suites; protected String protocol; @@ -147,9 +151,9 @@ public class EncryptionOptions ssl_context_factory = new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactory", new HashMap<>()); keystore = "conf/.keystore"; - keystore_password = "cassandra"; + keystore_password = null; truststore = "conf/.truststore"; - truststore_password = "cassandra"; + truststore_password = null; cipher_suites = null; protocol = null; accepted_protocols = null; @@ -608,6 +612,7 @@ public class EncryptionOptions @Replaces(oldName = "enable_legacy_ssl_storage_port", deprecated = true) public final boolean legacy_ssl_storage_port_enabled; public final String outbound_keystore; + @Nullable public final String outbound_keystore_password; public ServerEncryptionOptions() diff --git a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java index 826e3152f0..fdf669697c 100644 --- a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java +++ b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java @@ -123,22 +123,52 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto } } + /** + * Validates the given keystore password. + * + * @param isOutboundKeystore {@code true} for the {@code outbound_keystore_password};{@code false} otherwise + * @param password value + * @throws IllegalArgumentException if the {@code password} is empty as per the definition of {@link StringUtils#isEmpty(CharSequence)} + */ + protected void validatePassword(boolean isOutboundKeystore, String password) + { + boolean keystorePasswordEmpty = StringUtils.isEmpty(password); + if (keystorePasswordEmpty) + { + String keyName = isOutboundKeystore ? "outbound_" : ""; + final String msg = String.format("'%skeystore_password' must be specified", keyName); + throw new IllegalArgumentException(msg); + } + } + /** * Builds required KeyManagerFactory from the file based keystore. It also checks for the PrivateKey's certificate's * expiry and logs {@code warning} for each expired PrivateKey's certitificate. * * @return KeyManagerFactory built from the file based keystore. * @throws SSLException if any issues encountered during the build process + * @throws IllegalArgumentException if the validation for the {@code keystore_password} fails + * @see #validatePassword(boolean, String) */ @Override protected KeyManagerFactory buildKeyManagerFactory() throws SSLException { + /* + * Validation of the password is delayed until this point to allow nullable keystore passwords + * for other use-cases (CASSANDRA-18124). + */ + validatePassword(false, keystoreContext.password); return getKeyManagerFactory(keystoreContext); } @Override protected KeyManagerFactory buildOutboundKeyManagerFactory() throws SSLException { + /* + * Validation of the password is delayed until this point to allow nullable keystore passwords + * for other use-cases (CASSANDRA-18124). + */ + validatePassword(true, outboundKeystoreContext.password); return getKeyManagerFactory(outboundKeystoreContext); } @@ -156,7 +186,9 @@ public abstract class FileBasedSslContextFactory extends AbstractSslContextFacto final String algorithm = this.algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : this.algorithm; TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); KeyStore ts = KeyStore.getInstance(store_type); - ts.load(tsf, trustStoreContext.password.toCharArray()); + + final char[] truststorePassword = StringUtils.isEmpty(trustStoreContext.password) ? null : trustStoreContext.password.toCharArray(); + ts.load(tsf, truststorePassword); tmf.init(ts); return tmf; } diff --git a/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java b/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java index 2f0ead1b61..62e2d4cdac 100644 --- a/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java +++ b/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java @@ -109,11 +109,6 @@ public final class PEMBasedSslContextFactory extends FileBasedSslContextFactory final String msg = String.format("'%skeystore_password' and '%skey_password' both configurations are given and the values do not match", keyName, keyName); throw new IllegalArgumentException(msg); } - else - { - logger.warn("'{}keystore_password' and '{}key_password' both are configured but since the values match it's " + - "okay. Ideally you should only specify one of them.", keyName, keyName); - } } public PEMBasedSslContextFactory(Map parameters) diff --git a/src/java/org/apache/cassandra/security/PEMReader.java b/src/java/org/apache/cassandra/security/PEMReader.java index f539509393..aaeeab82e0 100644 --- a/src/java/org/apache/cassandra/security/PEMReader.java +++ b/src/java/org/apache/cassandra/security/PEMReader.java @@ -42,6 +42,7 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,7 +101,7 @@ public final class PEMReader String base64EncodedKey = extractBase64EncodedKey(pemKey); byte[] derKeyBytes = decodeBase64(base64EncodedKey); - if (keyPassword != null) + if (!StringUtils.isEmpty(keyPassword)) { logger.debug("Encrypted key's length: {}, key's password length: {}", derKeyBytes.length, keyPassword.length()); diff --git a/test/conf/cassandra-pem-sslcontextfactory-mismatching-passwords.yaml b/test/conf/cassandra-pem-sslcontextfactory-mismatching-passwords.yaml new file mode 100644 index 0000000000..5d64354208 --- /dev/null +++ b/test/conf/cassandra-pem-sslcontextfactory-mismatching-passwords.yaml @@ -0,0 +1,154 @@ +# +# 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 +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----- + private_key_password: "cassandra" + 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: cassandra-abracadbra +server_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + parameters: + private_key_password: "cassandra-abracadbra" + internode_encryption: none + keystore: test/conf/cassandra_ssl_test.keystore.pem + keystore_password: cassandra + 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-pem-sslcontextfactory-unencryptedkeys.yaml b/test/conf/cassandra-pem-sslcontextfactory-unencryptedkeys.yaml new file mode 100644 index 0000000000..7b08bcce03 --- /dev/null +++ b/test/conf/cassandra-pem-sslcontextfactory-unencryptedkeys.yaml @@ -0,0 +1,148 @@ +# +# 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 +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 PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw + efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv + wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047 + DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB + 9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi + G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM + B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL + uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU + l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt + bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y + G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF + FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u + 7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC + 0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W + ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8 + N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw + fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk + cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh + uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz + 9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv + hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR + QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9 + FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K + 37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj + Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr + RXKInnLInFD9JD94VqmMozo= + -----END 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----- +server_encryption_options: + ssl_context_factory: + class_name: org.apache.cassandra.security.PEMBasedSslContextFactory + internode_encryption: none + keystore: test/conf/cassandra_ssl_test.unencrypted_keystore.pem + 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/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java index 3edf9c188e..90eab980e4 100644 --- a/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java @@ -120,6 +120,7 @@ public class DefaultSslContextFactoryTest Map config = new HashMap<>(); config.putAll(commonConfig); config.put("keystore", "/this/is/probably/not/a/file/on/your/test/machine"); + config.put("keystore_password", "ThisWontMatter"); DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); defaultSslContextFactoryImpl.keystoreContext.checkedExpiry = false; @@ -168,6 +169,7 @@ public class DefaultSslContextFactoryTest Map config = new HashMap<>(); config.putAll(commonConfig); config.put("outbound_keystore", "/this/is/probably/not/a/file/on/your/test/machine"); + config.put("outbound_keystore_password", "ThisWontMatter"); DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); defaultSslContextFactoryImpl.outboundKeystoreContext.checkedExpiry = false; @@ -183,7 +185,7 @@ public class DefaultSslContextFactoryTest config.put("outbound_keystore_password", "HomeOfBadPasswords"); DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); - defaultSslContextFactoryImpl.buildKeyManagerFactory(); + defaultSslContextFactoryImpl.buildOutboundKeyManagerFactory(); } @Test diff --git a/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java new file mode 100644 index 0000000000..74811e66b2 --- /dev/null +++ b/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java @@ -0,0 +1,182 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLException; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.config.EncryptionOptions; +import org.apache.cassandra.config.ParameterizedClass; + +public class FileBasedSslContextFactoryTest +{ + private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactoryTest.class); + + private EncryptionOptions.ServerEncryptionOptions encryptionOptions; + + @BeforeClass + public static void setupDatabaseDescriptor() + { + System.setProperty("cassandra.config", "cassandra.yaml"); + } + + @AfterClass + public static void tearDownDatabaseDescriptor() + { + System.clearProperty("cassandra.config"); + } + + @Before + public void setup() + { + encryptionOptions = new EncryptionOptions.ServerEncryptionOptions() + .withSslContextFactory(new ParameterizedClass(TestFileBasedSSLContextFactory.class.getName(), + new HashMap<>())) + .withTrustStore("test/conf/cassandra_ssl_test.truststore") + .withTrustStorePassword("cassandra") + .withRequireClientAuth(false) + .withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA") + .withKeyStore("test/conf/cassandra_ssl_test.keystore") + .withKeyStorePassword("cassandra") + .withOutboundKeystore("test/conf/cassandra_ssl_test_outbound.keystore") + .withOutboundKeystorePassword("cassandra"); + } + + @Test + public void testHappyPath() throws SSLException + { + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions; + + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + Assert.assertNotNull("keystore_password must not be null", localEncryptionOptions.keystore_password); + Assert.assertNotNull("outbound_keystore_password must not be null", localEncryptionOptions.outbound_keystore_password); + + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + + /** + * Tests for empty {@code keystore_password} and empty {@code outbound_keystore_password} configurations. + */ + @Test(expected = IllegalArgumentException.class) + public void testEmptyKeystorePasswords() throws SSLException + { + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions.withKeyStorePassword(null).withOutboundKeystorePassword(null); + + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + Assert.assertNull("keystore_password must be null", localEncryptionOptions.keystore_password); + Assert.assertNull("outbound_keystore_password must be null", localEncryptionOptions.outbound_keystore_password); + + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + try + { + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + catch (Exception e) + { + Assert.assertEquals("'keystore_password' must be specified", e.getMessage()); + throw e; + } + } + + /** + * Tests for the empty password for the {@code keystore} used for the client communication. + */ + @Test(expected = IllegalArgumentException.class) + public void testEmptyKeystorePassword() throws SSLException + { + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions.withKeyStorePassword(null); + + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + Assert.assertNull("keystore_password must be null", localEncryptionOptions.keystore_password); + Assert.assertNotNull("outbound_keystore_password must not be null", localEncryptionOptions.outbound_keystore_password); + + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + try + { + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + catch (Exception e) + { + Assert.assertEquals("'keystore_password' must be specified", e.getMessage()); + throw e; + } + } + + /** + * Tests for the empty password for the {@code outbound_keystore}. Since the {@code outbound_keystore_password} defaults + * to the {@code keystore_password}, this test should pass without exceptions. + */ + @Test + public void testOnlyEmptyOutboundKeystorePassword() throws SSLException + { + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions.withOutboundKeystorePassword(null); + + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + Assert.assertNotNull("keystore_password must not be null", localEncryptionOptions.keystore_password); + Assert.assertNull("outbound_keystore_password must be null", localEncryptionOptions.outbound_keystore_password); + + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + sslContextFactory.buildKeyManagerFactory(); + sslContextFactory.buildTrustManagerFactory(); + } + + @Test + public void testEmptyTruststorePassword() throws SSLException + { + EncryptionOptions.ServerEncryptionOptions localEncryptionOptions = encryptionOptions.withTrustStorePassword(null); + Assert.assertEquals("org.apache.cassandra.security.FileBasedSslContextFactoryTest$TestFileBasedSSLContextFactory", + localEncryptionOptions.ssl_context_factory.class_name); + Assert.assertNotNull("keystore_password must not be null", localEncryptionOptions.keystore_password); + Assert.assertNotNull("outbound_keystore_password must not be null", localEncryptionOptions.outbound_keystore_password); + Assert.assertNull("truststore_password must be null", localEncryptionOptions.truststore_password); + + TestFileBasedSSLContextFactory sslContextFactory = + (TestFileBasedSSLContextFactory) localEncryptionOptions.sslContextFactoryInstance; + sslContextFactory.buildTrustManagerFactory(); + } + + public static class TestFileBasedSSLContextFactory extends FileBasedSslContextFactory + { + public TestFileBasedSSLContextFactory(Map parameters) + { + super(parameters); + } + } +} diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithMismatchingPasswordsTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithMismatchingPasswordsTest.java new file mode 100644 index 0000000000..c1e2dbe217 --- /dev/null +++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithMismatchingPasswordsTest.java @@ -0,0 +1,92 @@ +/* + * 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.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.exceptions.ConfigurationException; + +public class PEMBasedSslContextFactoryConfigWithMismatchingPasswordsTest +{ + @BeforeClass + public static void setupDatabaseDescriptor() + { + System.setProperty("cassandra.config", "cassandra-pem-sslcontextfactory-mismatching-passwords.yaml"); + } + + @AfterClass + public static void tearDownDatabaseDescriptor() + { + System.clearProperty("cassandra.config"); + } + + @Test(expected = ConfigurationException.class) + public void testInLinePEMConfiguration() throws SSLException + { + Config config = DatabaseDescriptor.loadConfig(); + try + { + config.client_encryption_options.applyConfig(); + } + catch (ConfigurationException e) + { + assertErrorMessageAndRethrow(e); + } + } + + @Test(expected = ConfigurationException.class) + public void testFileBasedPEMConfiguration() throws SSLException + { + Config config = DatabaseDescriptor.loadConfig(); + try + { + config.server_encryption_options.applyConfig(); + } + catch (ConfigurationException e) + { + assertErrorMessageAndRethrow(e); + } + } + + private void assertErrorMessageAndRethrow(ConfigurationException e) throws ConfigurationException + { + String expectedMessage = "'keystore_password' and 'key_password' both configurations are given and the values do not match"; + Throwable rootCause = getRootCause(e); + String actualMessage = rootCause.getMessage(); + Assert.assertEquals(expectedMessage, actualMessage); + throw e; + } + + private Throwable getRootCause(Throwable e) + { + Throwable rootCause = e; + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) + { + rootCause = rootCause.getCause(); + } + return rootCause; + } +} diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithUnencryptedKeysTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithUnencryptedKeysTest.java new file mode 100644 index 0000000000..066de8fea5 --- /dev/null +++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigWithUnencryptedKeysTest.java @@ -0,0 +1,79 @@ +/* + * 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.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.DatabaseDescriptor; + +public class PEMBasedSslContextFactoryConfigWithUnencryptedKeysTest +{ + @BeforeClass + public static void setupDatabaseDescriptor() + { + System.setProperty("cassandra.config", "cassandra-pem-sslcontextfactory-unencryptedkeys.yaml"); + System.setProperty("cassandra.disable_tcactive_openssl", "true"); + } + + @AfterClass + public static void tearDownDatabaseDescriptor() + { + System.clearProperty("cassandra.config"); + } + + @Test + public void testHappyPathInlinePEM() throws SSLException + { + + Config config = DatabaseDescriptor.loadConfig(); + config.client_encryption_options.applyConfig(); + + Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory", + config.client_encryption_options.ssl_context_factory.class_name); + Assert.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(); + + Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory", + config.server_encryption_options.ssl_context_factory.class_name); + Assert.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(); + } +}