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

Reply via email to