This is an automated email from the ASF dual-hosted git repository. smiklosovic pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit 531b4cde431c8521f4031811cebba5e7ee9750ed Merge: b7ec523aa9 bd49f6ff26 Author: Stefan Miklosovic <smikloso...@apache.org> AuthorDate: Mon Apr 17 10:22:31 2023 +0200 Merge branch 'cassandra-4.1' into trunk CHANGES.txt | 1 + NEWS.txt | 4 + conf/cassandra.yaml | 16 +- .../KubernetesSecretsPEMSslContextFactory.java | 4 +- .../KubernetesSecretsSslContextFactory.java | 25 +-- .../KubernetesSecretsPEMSslContextFactoryTest.java | 10 +- .../KubernetesSecretsSslContextFactoryTest.java | 32 ++-- .../apache/cassandra/config/EncryptionOptions.java | 9 +- .../security/FileBasedSslContextFactory.java | 34 +++- .../security/PEMBasedSslContextFactory.java | 5 - .../org/apache/cassandra/security/PEMReader.java | 3 +- ...em-sslcontextfactory-mismatching-passwords.yaml | 154 +++++++++++++++++ ...ndra-pem-sslcontextfactory-unencryptedkeys.yaml | 148 +++++++++++++++++ .../security/DefaultSslContextFactoryTest.java | 4 +- .../security/FileBasedSslContextFactoryTest.java | 182 +++++++++++++++++++++ ...tFactoryConfigWithMismatchingPasswordsTest.java | 92 +++++++++++ ...ontextFactoryConfigWithUnencryptedKeysTest.java | 79 +++++++++ 17 files changed, 755 insertions(+), 47 deletions(-) diff --cc CHANGES.txt index 48ad6d5463,79d3e8b287..49f52402cf --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,134 -1,5 +1,135 @@@ +5.0 + * Upgrade commons-io to 2.11.0 (CASSANDRA-17364) + * Node Draining Should Abort All Current SSTables Imports (CASSANDRA-18373) + * Use snake case for the names of CQL native functions (CASSANDRA-18037) + * Use jdk-dependent checkstyle version to check the source code (CASSANDRA-18262) + * Provide summary of failed SessionInfo's in StreamResultFuture (CASSANDRA-17199) + * CEP-20: Dynamic Data Masking (CASSANDRA-17940) + * Add system_views.snapshots virtual table (CASSANDRA-18102) + * Update OpenHFT dependencies (chronicle-queue, chronicle-core, chronicle-bytes, chronicle-wire, chronicle-threads) (CASSANDRA-18049) + * Remove org.apache.cassandra.hadoop code (CASSANDRA-18323) + * Remove deprecated CQL functions dateOf and unixTimestampOf (CASSANDRA-18328) + * Remove DateTieredCompactionStrategy (CASSANDRA-18043) + * Add system_views.max_sstable_size and system_views.max_sstable_duration tables (CASSANDRA-18333) + * Extend implicit allow-filtering for virtual tables to clustering columns (CASSANDRA-18331) + * Upgrade maven-shade-plugin to 3.4.1 to fix shaded dtest JAR build (CASSANDRA-18136) + * Upgrade to Opcodes.ASM9 (CASSANDRA-17971) + * Add MaxSSTableSize and MaxSSTableDuration metrics and propagate them together with local read/write ratio to tablestats (CASSANDRA-18283) + * Add more logging around CompactionManager operations (CASSANDRA-18268) + * Reduce memory allocations of calls to ByteBufer.duplicate() made in org.apache.cassandra.transport.CBUtil#writeValue (CASSANDRA-18212) + * CEP-17: SSTable API (CASSANDRA-17056) + * Gossip stateMapOrdering does not have correct ordering when both EndpointState are in the bootstrapping set (CASSANDRA-18292) + * Snapshot only sstables containing mismatching ranges on preview repair mismatch (CASSANDRA-17561) + * More accurate skipping of sstables in read path (CASSANDRA-18134) + * Prepare for JDK17 experimental support (CASSANDRA-18179, CASSANDRA-18258) + * Remove Scripted UDFs internals; hooks to be added later in CASSANDRA-17281 (CASSANDRA-18252) + * Update JNA to 5.13.0 (CASSANDRA-18050) + * Make virtual tables decide if they implicitly enable ALLOW FILTERING (CASSANDRA-18238) + * Add row, tombstone, and sstable count to nodetool profileload (CASSANDRA-18022) + * Coordinator level metrics for read response and mutation row and column counts (CASSANDRA-18155) + * Add CQL functions for dynamic data masking (CASSANDRA-17941) + * Print friendly error when nodetool attempts to connect to uninitialized server (CASSANDRA-11537) + * Use G1GC by default, and update default G1GC settings (CASSANDRA-18027) + * SimpleSeedProvider can resolve multiple IP addresses per DNS record (CASSANDRA-14361) + * Remove mocking in InternalNodeProbe spying on StorageServiceMBean (CASSANDRA-18152) + * Add compaction_properties column to system.compaction_history table and nodetool compactionhistory command (CASSANDRA-18061) + * Remove ProtocolVersion entirely from the CollectionSerializer ecosystem (CASSANDRA-18114) + * Fix serialization error in new getsstables --show-levels option (CASSANDRA-18140) + * Use checked casts when reading vints as ints (CASSANDRA-18099) + * Add Mutation Serialization Caching (CASSANDRA-17998) + * Only reload compaction strategies if disk boundaries change (CASSANDRA-17874) + * CEP-10: Simulator Java11 Support (CASSANDRA-17178) + * Set the major compaction type correctly for compactionstats (CASSANDRA-18055) + * Print exception message without stacktrace when nodetool commands fail on probe.getOwnershipWithPort() (CASSANDRA-18079) + * Add option to print level in nodetool getsstables output (CASSANDRA-18023) + * Implement a guardrail for not having zero default_time_to_live on tables with TWCS (CASSANDRA-18042) + * Add CQL scalar functions for collection aggregation (CASSANDRA-18060) + * Make cassandra.replayList property for CommitLogReplayer possible to react on keyspaces only (CASSANDRA-18044) + * Add Mathematical functions (CASSANDRA-17221) + * Make incremental backup configurable per table (CASSANDRA-15402) + * Change shebangs of Python scripts to resolve Python 3 from env command (CASSANDRA-17832) + * Add reasons to guardrail messages and consider guardrails in the error message for needed ALLOW FILTERING (CASSANDRA-17967) + * Add support for CQL functions on collections, tuples and UDTs (CASSANDRA-17811) + * Add flag to exclude nodes from local DC when running nodetool rebuild (CASSANDRA-17870) + * Adding endpoint verification option to client_encryption_options (CASSANDRA-18034) + * Replace 'wcwidth.py' with pypi module (CASSANDRA-17287) + * Add nodetool forcecompact to remove tombstoned or ttl'd data ignoring GC grace for given table and partition keys (CASSANDRA-17711) + * Offer IF (NOT) EXISTS in cqlsh completion for CREATE TYPE, DROP TYPE, CREATE ROLE and DROP ROLE (CASSANDRA-16640) + * Nodetool bootstrap resume will now return an error if the operation fails (CASSANDRA-16491) + * Disable resumable bootstrap by default (CASSANDRA-17679) + * Include Git SHA in --verbose flag for nodetool version (CASSANDRA-17753) + * Update Byteman to 4.0.20 and Jacoco to 0.8.8 (CASSANDRA-16413) + * Add memtable option among possible tab completions for a table (CASSANDRA-17982) + * Adds a trie-based memtable implementation (CASSANDRA-17240) + * Further improves precision of memtable heap tracking (CASSANDRA-17240) + * Fix formatting of metrics documentation (CASSANDRA-17961) + * Keep sstable level when streaming for decommission and move (CASSANDRA-17969) + * Add Unavailables metric for CASWrite in the docs (CASSANDRA-16357) + * Make Cassandra logs able to be viewed in the virtual table system_views.system_logs (CASSANDRA-17946) + * IllegalArgumentException in Gossiper#order due to concurrent mutations to elements being applied (CASSANDRA-17908) + * Include estimated active compaction remaining write size when starting a new compaction (CASSANDRA-17931) + * Mixed mode support for internode authentication during TLS upgrades (CASSANDRA-17923) + * Revert Mockito downgrade from CASSANDRA-17750 (CASSANDRA-17496) + * Add --older-than and --older-than-timestamp options for nodetool clearsnapshots (CASSANDRA-16860) + * Fix "open RT bound as its last item" exception (CASSANDRA-17810) + * Fix leak of non-standard Java types in JMX MBeans `org.apache.cassandra.db:type=StorageService` + and `org.apache.cassandra.db:type=RepairService` as clients using JMX cannot handle them. More details in NEWS.txt (CASSANDRA-17668) + * Deprecate Throwables.propagate usage (CASSANDRA-14218) + * Allow disabling hotness persistence for high sstable counts (CASSANDRA-17868) + * Prevent NullPointerException when changing neverPurgeTombstones from true to false (CASSANDRA-17897) + * Add metrics around storage usage and compression (CASSANDRA-17898) + * Remove usage of deprecated javax certificate classes (CASSANDRA-17867) + * Make sure preview repairs don't optimise streams unless configured to (CASSANDRA-17865) + * Optionally avoid hint transfer during decommission (CASSANDRA-17808) + * Make disabling auto snapshot on selected tables possible (CASSANDRA-10383) + * Introduce compaction priorities to prevent upgrade compaction inability to finish (CASSANDRA-17851) + * Prevent a user from manually removing ephemeral snapshots (CASSANDRA-17757) + * Remove dependency on Maven Ant Tasks (CASSANDRA-17750) + * Update ASM(9.1 to 9.3), Mockito(1.10.10 to 1.12.13) and ByteBuddy(3.2.4 to 4.7.0) (CASSANDRA-17835) + * Add the ability for operators to loosen the definition of "empty" for edge cases (CASSANDRA-17842) + * Fix potential out of range exception on column index downsampling (CASSANDRA-17839) + * Introduce target directory to vtable output for sstable_tasks and for compactionstats (CASSANDRA-13010) + * Read/Write/Truncate throw RequestFailure in a race condition with callback timeouts, should return Timeout instead (CASSANDRA-17828) + * Add ability to log load profiles at fixed intervals (CASSANDRA-17821) + * Protect against Gossip backing up due to a quarantined endpoint without version information (CASSANDRA-17830) + * NPE in org.apache.cassandra.cql3.Attributes.getTimeToLive (CASSANDRA-17822) + * Add guardrail for column size (CASSANDRA-17151) + * When doing a host replacement, we need to check that the node is a live node before failing with "Cannot replace a live node..." (CASSANDRA-17805) + * Add support to generate a One-Shot heap dump on unhandled exceptions (CASSANDRA-17795) + * Rate-limit new client connection auth setup to avoid overwhelming bcrypt (CASSANDRA-17812) + * DataOutputBuffer#scratchBuffer can use off-heap or on-heap memory as a means to control memory allocations (CASSANDRA-16471) + * Add ability to read the TTLs and write times of the elements of a collection and/or UDT (CASSANDRA-8877) + * Removed Python < 2.7 support from formatting.py (CASSANDRA-17694) + * Cleanup pylint issues with pylexotron.py (CASSANDRA-17779) + * NPE bug in streaming checking if SSTable is being repaired (CASSANDRA-17801) + * Users of NativeLibrary should handle lack of JNA appropriately when running in client mode (CASSANDRA-17794) + * Warn on unknown directories found in system keyspace directory rather than kill node during startup checks (CASSANDRA-17777) + * Log duplicate rows sharing a partition key found in verify and scrub (CASSANDRA-17789) + * Add separate thread pool for Secondary Index building so it doesn't block compactions (CASSANDRA-17781) + * Added JMX call to getSSTableCountPerTWCSBucket for TWCS (CASSANDRA-17774) + * When doing a host replacement, -Dcassandra.broadcast_interval_ms is used to know when to check the ring but checks that the ring wasn't changed in -Dcassandra.ring_delay_ms, changes to ring delay should not depend on when we publish load stats (CASSANDRA-17776) + * When bootstrap fails, CassandraRoleManager may attempt to do read queries that fail with "Cannot read from a bootstrapping node", and increments unavailables counters (CASSANDRA-17754) + * Add guardrail to disallow DROP KEYSPACE commands (CASSANDRA-17767) + * Remove ephemeral snapshot marker file and introduce a flag to SnapshotManifest (CASSANDRA-16911) + * Add a virtual table that exposes currently running queries (CASSANDRA-15241) + * Allow sstableloader to specify table without relying on path (CASSANDRA-16584) + * Fix TestGossipingPropertyFileSnitch.test_prefer_local_reconnect_on_listen_address (CASSANDRA-17700) + * Add ByteComparable API (CASSANDRA-6936) + * Add guardrail for maximum replication factor (CASSANDRA-17500) + * Increment CQLSH to version 6.2.0 for release 4.2 (CASSANDRA-17646) + * Adding support to perform certificate based internode authentication (CASSANDRA-17661) + * Option to disable CDC writes of repaired data (CASSANDRA-17666) + * When a node is bootstrapping it gets the whole gossip state but applies in random order causing some cases where StorageService will fail causing an instance to not show up in TokenMetadata (CASSANDRA-17676) + * Add CQLSH command SHOW REPLICAS (CASSANDRA-17577) + * Add guardrail to allow disabling of SimpleStrategy (CASSANDRA-17647) + * Change default directory permission to 750 in packaging (CASSANDRA-17470) + * Adding support for TLS client authentication for internode communication (CASSANDRA-17513) + * Add new CQL function maxWritetime (CASSANDRA-17425) + * Add guardrail for ALTER TABLE ADD / DROP / REMOVE column operations (CASSANDRA-17495) + * 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 --cc NEWS.txt index 848a0fa5ef,0930ac0cea..27b63c80c0 --- a/NEWS.txt +++ b/NEWS.txt @@@ -154,13 -69,6 +154,17 @@@ Upgradin upgrades involving 3.x and 4.x nodes. The fix for that issue makes it can now appear during rolling upgrades from 4.1.0 or 4.0.0-4.0.7. If that is your case, please use protocol v4 or higher in your driver. See CASSANDRA-17507 for further details. + - Added API for alternative sstable implementations. For details, see src/java/org/apache/cassandra/io/sstable/SSTable_API.md + - DateTieredCompactionStrategy was removed. Please change the compaction strategy for the tables using this strategy + to TimeWindowCompactionStrategy before upgrading to this version. + - The deprecated functions `dateOf` and `unixTimestampOf` have been removed. They were deprecated and replaced by + `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 --cc conf/cassandra.yaml index ee616e1674,a15de953c0..320fa06fcc --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@@ -1361,18 -1329,17 +1361,23 @@@ 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 + # 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 # 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 ++ #truststore_password: cassandra # Verify that the host name in the certificate matches the connected host require_endpoint_verification: false # More advanced defaults: @@@ -1406,10 -1373,14 +1411,15 @@@ 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 # Set trustore and truststore_password if require_client_auth is true # truststore: conf/.truststore # truststore_password: cassandra diff --cc examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java index fb11c91e90,fb11c91e90..18e211b3b8 --- 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,6 +125,8 @@@ public class KubernetesSecretsPEMSslCon private String pemEncodedCertificates; private PEMBasedSslContextFactory pemBasedSslContextFactory; ++ public boolean checkedExpiry = false; ++ public KubernetesSecretsPEMSslContextFactory() { pemBasedSslContextFactory = new PEMBasedSslContextFactory(); @@@ -165,7 -165,7 +167,7 @@@ protected KeyManagerFactory buildKeyManagerFactory() throws SSLException { KeyManagerFactory kmf = pemBasedSslContextFactory.buildKeyManagerFactory(); -- checkedExpiry = pemBasedSslContextFactory.checkedExpiry; ++ checkedExpiry = pemBasedSslContextFactory.keystoreContext.checkedExpiry; return kmf; } diff --cc examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java index c83fb03296,c83fb03296..ebeac6c4e8 --- 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 +149,12 @@@ public class KubernetesSecretsSslContex 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,12 +166,13 @@@ public KubernetesSecretsSslContextFactory(Map<String, Object> 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 --cc examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java index 2d127f32ea,2d127f32ea..f101e8955d --- 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 +147,7 @@@ public class KubernetesSecretsPEMSslCon KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); -- kubernetesSecretsSslContextFactory.checkedExpiry = false; ++ kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); } @@@ -158,7 -158,7 +158,7 @@@ 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 +172,7 @@@ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config); -- kubernetesSecretsSslContextFactory.checkedExpiry = false; ++ kubernetesSecretsSslContextFactory.keystoreContext.checkedExpiry = false; kubernetesSecretsSslContextFactory.buildKeyManagerFactory(); } @@@ -262,7 -262,7 +262,7 @@@ 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 +282,7 @@@ 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 --cc examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java index 8b22fff118,d37992a3c7..0c364196b8 --- 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 +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,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 org.apache.cassandra.io.util.File; 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 -63,11 +62,10 @@@ public class KubernetesSecretsSslContex private static void deleteFileIfExists(String file) { -- Path filePath = Paths.get(file); - boolean deleted = PathUtils.tryDelete(filePath); - boolean deleted = new File(filePath).toJavaIOFile().delete(); ++ 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 -106,7 +104,7 @@@ 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 -119,7 +117,7 @@@ 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 -133,7 +131,7 @@@ 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 -144,7 +142,7 @@@ config.putAll(commonConfig); KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config); -- kubernetesSecretsSslContextFactory.checkedExpiry = false; ++ kubernetesSecretsSslContextFactory.trustStoreContext.checkedExpiry = false; TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory(); Assert.assertNotNull(trustManagerFactory); } @@@ -155,9 -155,11 +153,11 @@@ Map<String, Object> 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 -183,20 +181,20 @@@ 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 -207,7 +205,7 @@@ 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 -227,7 +225,7 @@@ 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 --cc src/java/org/apache/cassandra/config/EncryptionOptions.java index 0ab653f088,2610ff68f2..afa0d66bac --- a/src/java/org/apache/cassandra/config/EncryptionOptions.java +++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java @@@ -607,8 -607,6 +611,9 @@@ public class EncryptionOption public final InternodeEncryption internode_encryption; @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 --cc src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java index 826e3152f0,9876eb4b89..fdf669697c --- a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java +++ b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java @@@ -123,23 -119,55 +123,53 @@@ public abstract class FileBasedSslConte } } + /** + * 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(String password) ++ protected void validatePassword(boolean isOutboundKeystore, String password) + { + boolean keystorePasswordEmpty = StringUtils.isEmpty(password); + if (keystorePasswordEmpty) + { - throw new IllegalArgumentException("'keystore_password' must be specified"); ++ 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(keystore_password); ++ validatePassword(false, keystoreContext.password); + return getKeyManagerFactory(keystoreContext); + } - try (InputStream ksf = Files.newInputStream(File.getPath(keystore))) - { - final String algorithm = this.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : this.algorithm; - KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); - KeyStore ks = KeyStore.getInstance(store_type); - ks.load(ksf, keystore_password.toCharArray()); - if (!checkedExpiry) - { - checkExpiredCerts(ks); - checkedExpiry = true; - } - kmf.init(ks, keystore_password.toCharArray()); - return kmf; - } - catch (Exception e) - { - throw new SSLException("failed to build key manager store for secure connections", e); - } + @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 -184,9 +186,9 @@@ 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(truststore_password) ? null : truststore_password.toCharArray(); ++ final char[] truststorePassword = StringUtils.isEmpty(trustStoreContext.password) ? null : trustStoreContext.password.toCharArray(); + ts.load(tsf, truststorePassword); tmf.init(ts); return tmf; } diff --cc src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java index 2f0ead1b61,d62aef5a1c..62e2d4cdac --- a/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java +++ b/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java @@@ -97,40 -99,22 +97,35 @@@ public final class PEMBasedSslContextFa { } - public PEMBasedSslContextFactory(Map<String, Object> parameters) + private void validatePasswords() { - super(parameters); - pemEncodedKey = getString(ConfigKey.ENCODED_KEY.getKeyName()); - keyPassword = getString(ConfigKey.KEY_PASSWORD.getKeyName()); - if (StringUtils.isEmpty(keyPassword)) - { - keyPassword = keystore_password; - } - else if (!StringUtils.isEmpty(keystore_password) && !keyPassword.equals(keystore_password)) + boolean shouldThrow = !keystoreContext.passwordMatchesIfPresent(pemEncodedKeyContext.password) + || !outboundKeystoreContext.passwordMatchesIfPresent(pemEncodedOutboundKeyContext.password); + boolean outboundPasswordMismatch = !outboundKeystoreContext.passwordMatchesIfPresent(pemEncodedOutboundKeyContext.password); + String keyName = outboundPasswordMismatch ? "outbound_" : ""; + + if (shouldThrow) { - throw new IllegalArgumentException("'keystore_password' and 'key_password' both configurations are given and the " + - "values do not match"); + 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<String, Object> parameters) + { + super(parameters); + final String pemEncodedKey = getString(ConfigKey.ENCODED_KEY.getKeyName()); + final String pemEncodedKeyPassword = StringUtils.defaultString(getString(ConfigKey.KEY_PASSWORD.getKeyName()), keystoreContext.password); + pemEncodedKeyContext = new PEMBasedKeyStoreContext(pemEncodedKey, pemEncodedKeyPassword, StringUtils.isEmpty(pemEncodedKey), keystoreContext); + + final String pemEncodedOutboundKey = StringUtils.defaultString(getString(ConfigKey.OUTBOUND_ENCODED_KEY.getKeyName()), pemEncodedKey); + final String outboundKeyPassword = StringUtils.defaultString(StringUtils.defaultString(getString(ConfigKey.OUTBOUND_ENCODED_KEY_PASSWORD.getKeyName()), + outboundKeystoreContext.password), pemEncodedKeyPassword); + pemEncodedOutboundKeyContext = new PEMBasedKeyStoreContext(pemEncodedKey, outboundKeyPassword, StringUtils.isEmpty(pemEncodedOutboundKey), outboundKeystoreContext); + + validatePasswords(); - if (!StringUtils.isEmpty(truststore_password)) + if (!StringUtils.isEmpty(trustStoreContext.password)) { logger.warn("PEM based truststore should not be using password. Ignoring the given value in " + "'truststore_password' configuration."); diff --cc test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java index 3edf9c188e,0657eb67a6..90eab980e4 --- a/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/DefaultSslContextFactoryTest.java @@@ -120,9 -112,10 +120,10 @@@ public class DefaultSslContextFactoryTe Map<String,Object> config = new HashMap<>(); config.putAll(commonConfig); config.put("keystore", "/this/is/probably/not/a/file/on/your/test/machine"); - config.put("keystore_password","ThisWontMatter"); ++ config.put("keystore_password", "ThisWontMatter"); DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); - defaultSslContextFactoryImpl.checkedExpiry = false; + defaultSslContextFactoryImpl.keystoreContext.checkedExpiry = false; defaultSslContextFactoryImpl.buildKeyManagerFactory(); } @@@ -157,59 -150,9 +158,60 @@@ // Make sure that new factory object preforms the fresh private key expiry check DefaultSslContextFactory defaultSslContextFactoryImpl3 = new DefaultSslContextFactory(config); - Assert.assertFalse(defaultSslContextFactoryImpl3.checkedExpiry); + Assert.assertFalse(defaultSslContextFactoryImpl3.keystoreContext.checkedExpiry); defaultSslContextFactoryImpl3.buildKeyManagerFactory(); - Assert.assertTrue(defaultSslContextFactoryImpl3.checkedExpiry); + Assert.assertTrue(defaultSslContextFactoryImpl3.keystoreContext.checkedExpiry); + } + + @Test(expected = IOException.class) + public void buildOutboundKeyManagerFactoryWithInvalidKeystoreFile() throws IOException + { + Map<String, Object> 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; + defaultSslContextFactoryImpl.buildOutboundKeyManagerFactory(); + } + + @Test(expected = IOException.class) + public void buildOutboundKeyManagerFactoryWithBadPassword() throws IOException + { + Map<String, Object> config = new HashMap<>(); + config.putAll(commonConfig); + addOutboundKeystoreOptions(config); + config.put("outbound_keystore_password", "HomeOfBadPasswords"); + + DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); - defaultSslContextFactoryImpl.buildKeyManagerFactory(); ++ defaultSslContextFactoryImpl.buildOutboundKeyManagerFactory(); + } + + @Test + public void buildOutboundKeyManagerFactoryHappyPath() throws IOException + { + Map<String, Object> config = new HashMap<>(); + config.putAll(commonConfig); + + DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config); + // Make sure the exiry check didn't happen so far for the private key + Assert.assertFalse(defaultSslContextFactoryImpl.outboundKeystoreContext.checkedExpiry); + + addOutboundKeystoreOptions(config); + DefaultSslContextFactory defaultSslContextFactoryImpl2 = new DefaultSslContextFactory(config); + // Trigger the private key loading. That will also check for expired private key + defaultSslContextFactoryImpl2.buildOutboundKeyManagerFactory(); + // Now we should have checked the private key's expiry + Assert.assertTrue(defaultSslContextFactoryImpl2.outboundKeystoreContext.checkedExpiry); + Assert.assertFalse(defaultSslContextFactoryImpl2.keystoreContext.checkedExpiry); + + // Make sure that new factory object preforms the fresh private key expiry check + DefaultSslContextFactory defaultSslContextFactoryImpl3 = new DefaultSslContextFactory(config); + Assert.assertFalse(defaultSslContextFactoryImpl3.outboundKeystoreContext.checkedExpiry); + defaultSslContextFactoryImpl3.buildOutboundKeyManagerFactory(); + Assert.assertTrue(defaultSslContextFactoryImpl3.outboundKeystoreContext.checkedExpiry); + Assert.assertFalse(defaultSslContextFactoryImpl2.keystoreContext.checkedExpiry); } @Test diff --cc test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java index 0000000000,be49d163eb..74811e66b2 mode 000000,100644..100644 --- a/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java +++ b/test/unit/org/apache/cassandra/security/FileBasedSslContextFactoryTest.java @@@ -1,0 -1,156 +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"); ++ .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); ++ 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<String, Object> parameters) + { + super(parameters); + } + } + } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org