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


The following commit(s) were added to refs/heads/trunk by this push:
     new 6ab45971fc Introduce pluggable crypto providers and default to Amazon 
Corretto Crypto Provider
6ab45971fc is described below

commit 6ab45971fc651f78c8748f80e3cd6d4a1b6dbc50
Author: ayushis <ayus...@netflix.com>
AuthorDate: Mon Jul 10 15:21:07 2023 -0700

    Introduce pluggable crypto providers and default to Amazon Corretto Crypto 
Provider
    
    patch by Ayushi Singh; reviewed by Stefan Miklosovic, Michael Semb Wever 
and Maxim Muzafarov for CASSANDRA-18624
    
    Co-authored-by: Stefan Miklosovic <smikloso...@apache.org>
---
 .build/build-resolver.xml                          |  32 +-
 .build/parent-pom-template.xml                     |  41 +++
 CHANGES.txt                                        |   1 +
 NEWS.txt                                           |   7 +
 bin/cassandra.in.sh                                |   6 +
 conf/cassandra.yaml                                |  15 +
 debian/cassandra.install                           |   2 +
 redhat/cassandra.in.sh                             |   6 +
 .../config/CassandraRelevantProperties.java        |   3 +
 src/java/org/apache/cassandra/config/Config.java   |   1 +
 .../cassandra/config/DatabaseDescriptor.java       |  53 +++-
 .../cassandra/security/AbstractCryptoProvider.java | 205 ++++++++++++
 .../cassandra/security/DefaultCryptoProvider.java  |  67 ++++
 .../org/apache/cassandra/security/JREProvider.java |  57 ++++
 .../org/apache/cassandra/utils/FBUtilities.java    |  21 ++
 .../cassandra/distributed/impl/Instance.java       |   2 +
 .../distributed/test/CryptoProviderTest.java       | 203 ++++++++++++
 .../config/DatabaseDescriptorRefTest.java          |   1 +
 .../cassandra/security/CryptoProviderTest.java     | 343 +++++++++++++++++++++
 .../cassandra/security/InvalidCryptoProvider.java  |  53 ++++
 .../apache/cassandra/security/TestJREProvider.java |  42 +++
 21 files changed, 1156 insertions(+), 5 deletions(-)

diff --git a/.build/build-resolver.xml b/.build/build-resolver.xml
index 468adf76bf..5b95addb0d 100644
--- a/.build/build-resolver.xml
+++ b/.build/build-resolver.xml
@@ -64,7 +64,7 @@
 
         <macrodef name="resolve">
             <!--
-              maven-resolver-ant-tasks's resolve logic doesn't have retry 
logic and does not respect settings.xml, 
+              maven-resolver-ant-tasks's resolve logic doesn't have retry 
logic and does not respect settings.xml,
               this causes issues when overriding maven central is required 
(such as when behind a corporate firewall);
               it is critical to always provide the 'all' remoterepos to 
override resolve's default hard coded logic.
 
@@ -74,7 +74,7 @@
             <element name="elements" implicit="yes"/>
             <sequential>
                 <retry retrycount="3">
-                  <resolver:resolve 
failonmissingattachments="@{failonmissingattachments}">
+                    <resolver:resolve 
failonmissingattachments="@{failonmissingattachments}">
                         <resolver:remoterepos refid="all"/>
                         <elements/>
                     </resolver:resolve>
@@ -88,8 +88,8 @@
             <sequential>
                 <retry retrycount="3">
                     <resolver:pom file="@{file}" id="@{id}">
-                          <remoterepos refid="all"/>
-                          <elements/>
+                        <remoterepos refid="all"/>
+                        <elements/>
                     </resolver:pom>
                 </retry>
             </sequential>
@@ -206,6 +206,26 @@
         </retry>
         <mkdir dir="${local.repository}/org/apache/cassandra/deps/sigar-bin"/>
         <mkdir dir="${build.lib}/sigar-bin"/>
+        <mkdir dir="${build.lib}/x86_64"/>
+        <mkdir dir="${build.lib}/aarch64"/> <!-- uname -m on arm prints 
aarch64 instead of aarch_64 -->
+
+        <!-- artifacts needs AmazonCorrettoCryptoProvider for multiple archs 
-->
+        <retry retrycount="3" retrydelay="10" >
+            <resolve>
+                <dependencies>
+                    <dependency groupId="software.amazon.cryptools" 
artifactId="AmazonCorrettoCryptoProvider" version="2.2.0" 
classifier="linux-x86_64" />
+                </dependencies>
+                <files dir="${build.lib}/x86_64" 
layout="{artifactId}-{version}-{classifier}.{extension}" />
+            </resolve>
+        </retry>
+        <retry retrycount="3" retrydelay="10" >
+            <resolve>
+                <dependencies>
+                    <dependency groupId="software.amazon.cryptools" 
artifactId="AmazonCorrettoCryptoProvider" version="2.2.0" 
classifier="linux-aarch_64" />
+                </dependencies>
+                <files dir="${build.lib}/aarch64" 
layout="{artifactId}-{version}-{classifier}.{extension}" />
+            </resolve>
+        </retry>
 
         <retry retrycount="3" retrydelay="10" >
             <antcall target="_resolver-dist-lib_get_files"/>
@@ -240,6 +260,10 @@
             <file 
file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-linux.so"/>
             <file 
file="${local.repository}/org/apache/cassandra/deps/sigar-bin/libsigar-x86-solaris.so"/>
         </copy>
+
+        <!-- as resolver will copy all dependencies into lib dir, and we are 
copying jars to lib/{x86_64|aarch64} as well, we would have duplicities -->
+        <delete 
file="${build.lib}/AmazonCorrettoCryptoProvider-2.2.0-linux-x86_64.jar" 
failonerror="false"/>
+        <delete 
file="${build.lib}/AmazonCorrettoCryptoProvider-2.2.0-linux-aarch_64.jar" 
failonerror="false"/>
     </target>
 
     <target name="_resolver-dist-lib_get_files">
diff --git a/.build/parent-pom-template.xml b/.build/parent-pom-template.xml
index 50695a348d..4c54e5d94b 100644
--- a/.build/parent-pom-template.xml
+++ b/.build/parent-pom-template.xml
@@ -233,12 +233,53 @@
       <id>zznate</id>
       <name>Nate McCall</name>
     </developer>
+    <developer>
+      <id>smiklosovic</id>
+      <name>Stefan Miklosovic</name>
+    </developer>
   </developers>
   <scm>
     
<connection>scm:https://gitbox.apache.org/repos/asf/cassandra.git</connection>
     
<developerConnection>scm:https://gitbox.apache.org/repos/asf/cassandra.git</developerConnection>
     <url>https://gitbox.apache.org/repos/asf?p=cassandra.git;a=tree</url>
   </scm>
+
+  <profiles>
+    <profile>
+      <id>x86_64</id>
+      <activation>
+        <os>
+          <!-- we need something as a default even if it doesn't successfully 
load the .so files. -->
+          <arch>!aarch64</arch>
+        </os>
+      </activation>
+      <dependencies>
+        <dependency>
+          <groupId>software.amazon.cryptools</groupId>
+          <artifactId>AmazonCorrettoCryptoProvider</artifactId>
+          <classifier>linux-x86_64</classifier>
+          <version>2.2.0</version>
+        </dependency>
+      </dependencies>
+    </profile>
+    <profile>
+      <id>aarch_64</id>
+      <activation>
+        <os>
+          <arch>aarch64</arch>
+        </os>
+      </activation>
+      <dependencies>
+        <dependency>
+          <groupId>software.amazon.cryptools</groupId>
+          <artifactId>AmazonCorrettoCryptoProvider</artifactId>
+          <classifier>linux-aarch_64</classifier>
+          <version>2.2.0</version>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
+
   <dependencyManagement>
     <!--
     Dependency metadata is specified here (version, scope, exclusions, etc.), 
then referenced in child POMs by groupId and
diff --git a/CHANGES.txt b/CHANGES.txt
index 683010434d..de5cd3768a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.0
+ * Introduce pluggable crypto providers and default to 
AmazonCorrettoCryptoProvider (CASSANDRA-18624)
  * Improved DeletionTime serialization (CASSANDRA-18648)
  * CEP-7: Storage Attached Indexes (CASSANDRA-16052)
  * Add equals/hashCode override for ServerEncryptionOptions (CASSANDRA-18428)
diff --git a/NEWS.txt b/NEWS.txt
index 91fac33c98..1c5be180e8 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -71,6 +71,9 @@ using the provided 'sstableupgrade' tool.
 
 New features
 ------------
+    - Pluggable crypto providers were made possible via `crypto_provider` 
section in cassandra.yaml. The default provider is 
+      Amazon Corretto Crypto Provider and it is installed automatically upon 
node's start. Only x86_64 and aarch64 architectures are supported now.
+      Please consult upgrade section to know more details when upgrading from 
older Cassandra versions.
     - Added a new secondary index implementation, Storage-Attached Indexes 
(SAI). Overview documentation and a basic
       tutorial can be found at 
src/java/org/apache/cassandra/index/sai/README.md.
     - *Experimental* support for Java 17 has been added. JVM options that 
differ between or are
@@ -210,6 +213,10 @@ Upgrading
      Consult cassandra-rackdc.properties for more details. (CASSANDRA-16555)
     - JMX MBean `org.apache.cassandra.metrics:type=BufferPool` without scope 
has been removed.
       Use instead 
`org.apache.cassandra.metrics:type=BufferPool,scope=chunk-cache`.  
(CASSANDRA-17668)
+    - Upon upgrade, when cassandra.yaml does not contain `crypto_provider` 
configuration section, crypto providers from JRE installation will be used
+      and no installation of DefaultCryptoProvider installing Amazon Corretto 
Crypto Provider will be conducted. 
+      You need to explicitly add this section to the old yaml if it does not 
contain it yet to enable Amazon Corretto Crypto Provider for such node. 
+      New deployments have `crypto_provider` uncommented with 
DefaultCryptoProvider hence Corretto provider will be installed automatically 
for corresponding architecture.
 
 
 Deprecation
diff --git a/bin/cassandra.in.sh b/bin/cassandra.in.sh
index ee7a8e223c..dfa17643fd 100644
--- a/bin/cassandra.in.sh
+++ b/bin/cassandra.in.sh
@@ -84,6 +84,12 @@ JAVA_AGENT="$JAVA_AGENT 
-javaagent:$CASSANDRA_HOME/lib/jamm-0.4.0.jar"
 # Added sigar-bin to the java.library.path CASSANDRA-7838
 JAVA_OPTS="$JAVA_OPTS:-Djava.library.path=$CASSANDRA_HOME/lib/sigar-bin"
 
+platform=$(uname -m)
+if [ -d "$CASSANDRA_HOME"/lib/"$platform" ]; then
+    for jar in "$CASSANDRA_HOME"/lib/"$platform"/*.jar ; do
+        CLASSPATH="$CLASSPATH:${jar}"
+    done
+fi
 
 #
 # Java executable and per-Java version JVM settings
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index a9d0ddadbc..6414fab956 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -1375,6 +1375,21 @@ dynamic_snitch_reset_interval: 600000ms
 # until the pinned host was 20% worse than the fastest.
 dynamic_snitch_badness_threshold: 1.0
 
+# Configures Java crypto provider. By default, it will use 
DefaultCryptoProvider
+# which will install Amazon Correto Crypto Provider.
+#
+# Amazon Correto Crypto Provider works currently for x86_64 and aarch_64 
platforms.
+# If this provider fails it will fall back to the default crypto provider in 
the JRE.
+#
+# To force failure when the provider was not installed properly, set the 
property "fail_on_missing_provider" to "true".
+#
+# To bypass the installation of a crypto provider use class 
'org.apache.cassandra.security.JREProvider'
+#
+crypto_provider:
+  - class_name: org.apache.cassandra.security.DefaultCryptoProvider
+    parameters:
+      - fail_on_missing_provider: "false"
+
 # Configure server-to-server internode encryption
 #
 # JVM and netty defaults for supported SSL socket protocols and cipher suites 
can
diff --git a/debian/cassandra.install b/debian/cassandra.install
index dced5a29e2..abba6a7843 100644
--- a/debian/cassandra.install
+++ b/debian/cassandra.install
@@ -29,3 +29,5 @@ tools/bin/sstablepartitions usr/bin
 lib/*.jar usr/share/cassandra/lib
 lib/*.zip usr/share/cassandra/lib
 lib/sigar-bin/* usr/share/cassandra/lib/sigar-bin
+lib/x86_64/* usr/share/cassandra/lib/x86_64
+lib/aarch64/* usr/share/cassandra/lib/aarch64
diff --git a/redhat/cassandra.in.sh b/redhat/cassandra.in.sh
index fed5d4384e..8ec1905ac0 100644
--- a/redhat/cassandra.in.sh
+++ b/redhat/cassandra.in.sh
@@ -42,6 +42,12 @@ CLASSPATH="$CLASSPATH:$EXTRA_CLASSPATH"
 # set JVM javaagent opts to avoid warnings/errors
 JAVA_AGENT="$JAVA_AGENT -javaagent:$CASSANDRA_HOME/lib/jamm-0.4.0.jar"
 
+platform=$(uname -m)
+if [ -d "$CASSANDRA_HOME"/lib/"$platform" ]; then
+    for jar in "$CASSANDRA_HOME"/lib/"$platform"/*.jar ; do
+        CLASSPATH="$CLASSPATH:${jar}"
+    done
+fi
 
 #
 # Java executable and per-Java version JVM settings
diff --git 
a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java 
b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
index 5d5c14270a..b763899d7d 100644
--- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
+++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
@@ -170,6 +170,7 @@ public enum CassandraRelevantProperties
     CONSISTENT_DIRECTORY_LISTINGS("cassandra.consistent_directory_listings"),
     CONSISTENT_RANGE_MOVEMENT("cassandra.consistent.rangemovement", "true"),
     
CONSISTENT_SIMULTANEOUS_MOVES_ALLOW("cassandra.consistent.simultaneousmoves.allow"),
+    CRYPTO_PROVIDER_CLASS_NAME("cassandra.crypto_provider_class_name"),
     
CUSTOM_GUARDRAILS_CONFIG_PROVIDER_CLASS("cassandra.custom_guardrails_config_provider_class"),
     CUSTOM_QUERY_HANDLER_CLASS("cassandra.custom_query_handler_class"),
     CUSTOM_TRACING_CLASS("cassandra.custom_tracing_class"),
@@ -212,6 +213,7 @@ public enum CassandraRelevantProperties
     
EXPIRATION_DATE_OVERFLOW_POLICY("cassandra.expiration_date_overflow_policy"),
     
EXPIRATION_OVERFLOW_WARNING_INTERVAL_MINUTES("cassandra.expiration_overflow_warning_interval_minutes",
 "5"),
     
FAILURE_LOGGING_INTERVAL_SECONDS("cassandra.request_failure_log_interval_seconds",
 "60"),
+    
FAIL_ON_MISSING_CRYPTO_PROVIDER("cassandra.fail_on_missing_crypto_provider", 
"false"),
     FD_INITIAL_VALUE_MS("cassandra.fd_initial_value_ms"),
     FD_MAX_INTERVAL_MS("cassandra.fd_max_interval_ms"),
     FILE_CACHE_ENABLED("cassandra.file_cache_enabled"),
@@ -528,6 +530,7 @@ public enum CassandraRelevantProperties
     
TEST_SIMULATOR_PRINT_ASM_CLASSES("cassandra.test.simulator.print_asm_classes", 
""),
     TEST_SIMULATOR_PRINT_ASM_OPTS("cassandra.test.simulator.print_asm_opts", 
""),
     TEST_SIMULATOR_PRINT_ASM_TYPES("cassandra.test.simulator.print_asm_types", 
""),
+    
TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION("cassandra.test.security.skip.provider.installation",
 "false"),
     TEST_SSTABLE_FORMAT_DEVELOPMENT("cassandra.test.sstableformatdevelopment"),
     TEST_STRICT_LCS_CHECKS("cassandra.test.strict_lcs_checks"),
     /** Turns some warnings into exceptions for testing. */
diff --git a/src/java/org/apache/cassandra/config/Config.java 
b/src/java/org/apache/cassandra/config/Config.java
index 5b3bc81887..e254e54e05 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -80,6 +80,7 @@ public class Config
     public String authenticator;
     public String authorizer;
     public String role_manager;
+    public ParameterizedClass crypto_provider;
     public String network_authorizer;
     @Replaces(oldName = "permissions_validity_in_ms", converter = 
Converters.MILLIS_DURATION_INT, deprecated = true)
     public volatile DurationSpec.IntMillisecondsBound permissions_validity = 
new DurationSpec.IntMillisecondsBound("2s");
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java 
b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 4e5a26fafc..558f4a349b 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -98,7 +98,9 @@ import org.apache.cassandra.locator.IEndpointSnitch;
 import org.apache.cassandra.locator.InetAddressAndPort;
 import org.apache.cassandra.locator.Replica;
 import org.apache.cassandra.locator.SeedProvider;
+import org.apache.cassandra.security.AbstractCryptoProvider;
 import org.apache.cassandra.security.EncryptionContext;
+import org.apache.cassandra.security.JREProvider;
 import org.apache.cassandra.security.SSLFactory;
 import org.apache.cassandra.service.CacheService.CacheType;
 import org.apache.cassandra.service.paxos.Paxos;
@@ -123,10 +125,11 @@ import static 
org.apache.cassandra.config.CassandraRelevantProperties.SEARCH_CON
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.SSL_STORAGE_PORT;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.STORAGE_DIR;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.STORAGE_PORT;
-import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_STRICT_RUNTIME_CHECKS;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.SUN_ARCH_DATA_MODEL;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_FAIL_MV_LOCKS_COUNT;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_JVM_DTEST_DISABLE_SSL;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_STRICT_RUNTIME_CHECKS;
 import static 
org.apache.cassandra.config.CassandraRelevantProperties.UNSAFE_SYSTEM;
 import static 
org.apache.cassandra.config.DataRateSpec.DataRateUnit.BYTES_PER_SECOND;
 import static 
org.apache.cassandra.config.DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND;
@@ -174,6 +177,7 @@ public class DatabaseDescriptor
 
     private static Config.DiskAccessMode indexAccessMode;
 
+    private static AbstractCryptoProvider cryptoProvider;
     private static IAuthenticator authenticator;
     private static IAuthorizer authorizer;
     private static INetworkAuthorizer networkAuthorizer;
@@ -426,6 +430,8 @@ public class DatabaseDescriptor
         //InetAddressAndPort cares that applySimpleConfig runs first
         applySSTableFormats();
 
+        applyCryptoProvider();
+
         applySimpleConfig();
 
         applyPartitioner();
@@ -1219,6 +1225,42 @@ public class DatabaseDescriptor
         }
     }
 
+    public static void applyCryptoProvider()
+    {
+        if (TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION.getBoolean())
+            return;
+
+        if (conf.crypto_provider == null)
+            conf.crypto_provider = new 
ParameterizedClass(JREProvider.class.getName(), null);
+
+        // properties beat configuration
+        String classNameFromSystemProperties = 
CassandraRelevantProperties.CRYPTO_PROVIDER_CLASS_NAME.getString();
+        if (classNameFromSystemProperties != null)
+            conf.crypto_provider.class_name = classNameFromSystemProperties;
+
+        if (conf.crypto_provider.class_name == null)
+            throw new ConfigurationException("Failed to initialize crypto 
provider, class_name cannot be null");
+
+        if (conf.crypto_provider.parameters == null)
+            conf.crypto_provider.parameters = new HashMap<>();
+
+        Map<String, String> cryptoProviderParameters = new 
HashMap<>(conf.crypto_provider.parameters);
+        
cryptoProviderParameters.putIfAbsent(AbstractCryptoProvider.FAIL_ON_MISSING_PROVIDER_KEY,
 "false");
+
+        try
+        {
+            cryptoProvider = 
FBUtilities.newCryptoProvider(conf.crypto_provider.class_name, 
cryptoProviderParameters);
+            cryptoProvider.install();
+        }
+        catch (Exception e)
+        {
+            if (e instanceof ConfigurationException)
+                throw (ConfigurationException) e;
+            else
+                throw new ConfigurationException(String.format("Failed to 
initialize crypto provider %s", conf.crypto_provider.class_name), e);
+        }
+    }
+
     public static void applySeedProvider()
     {
         // load the seeds for node contact points
@@ -1502,6 +1544,15 @@ public class DatabaseDescriptor
         return detector;
     }
 
+    public static AbstractCryptoProvider getCryptoProvider()
+    {
+        return cryptoProvider;
+    }
+
+    public static void setCryptoProvider(AbstractCryptoProvider cryptoProvider)
+    {
+        DatabaseDescriptor.cryptoProvider = cryptoProvider;
+    }
     public static IAuthenticator getAuthenticator()
     {
         return authenticator;
diff --git a/src/java/org/apache/cassandra/security/AbstractCryptoProvider.java 
b/src/java/org/apache/cassandra/security/AbstractCryptoProvider.java
new file mode 100644
index 0000000000..1c437e6f2d
--- /dev/null
+++ b/src/java/org/apache/cassandra/security/AbstractCryptoProvider.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.utils.FBUtilities;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.FAIL_ON_MISSING_CRYPTO_PROVIDER;
+
+public abstract class AbstractCryptoProvider
+{
+    protected static final Logger logger = 
LoggerFactory.getLogger(AbstractCryptoProvider.class);
+    public static final String FAIL_ON_MISSING_PROVIDER_KEY = 
"fail_on_missing_provider";
+
+    protected final boolean failOnMissingProvider;
+    private final Map<String, String> properties;
+
+    public AbstractCryptoProvider(Map<String, String> args)
+    {
+        this.properties = args == null ? new HashMap<>() : args;
+        boolean failOnMissingProviderFromProperties = 
Boolean.parseBoolean(this.properties.getOrDefault(FAIL_ON_MISSING_PROVIDER_KEY, 
"false"));
+        failOnMissingProvider = 
FAIL_ON_MISSING_CRYPTO_PROVIDER.getBoolean(failOnMissingProviderFromProperties);
+    }
+
+    /**
+     * Returns unmodifiable properties of this crypto provider
+     *
+     * @return crypto provider properties
+     */
+    public Map<String, String> getProperties()
+    {
+        return Collections.unmodifiableMap(properties);
+    }
+
+    /**
+     * Returns name of the provider, as returned from {@link 
Provider#getName()}
+     *
+     * @return name of the provider
+     */
+    public abstract String getProviderName();
+
+    /**
+     * Returns the name of the class which installs specific provider of name 
{@link #getProviderName()}.
+     *
+     * @return name of class of provider
+     */
+    public abstract String getProviderClassAsString();
+
+    /**
+     * Returns a runnable which installs this crypto provider.
+     *
+     * @return runnable which installs this provider
+     */
+    protected abstract Runnable installator();
+
+    /**
+     * Returns boolean telling if this provider was installed properly.
+     *
+     * @return {@code true} if provider was installed properly, {@code false} 
otherwise.
+     */
+    protected abstract boolean isHealthyInstallation() throws Exception;
+
+    /**
+     * The default installation runs {@link 
AbstractCryptoProvider#installator()} and after that
+     * {@link AbstractCryptoProvider#isHealthyInstallation()}.
+     * <p>
+     * If any step fails, it will not throw an exception unless the parameter
+     * {@link AbstractCryptoProvider#FAIL_ON_MISSING_PROVIDER_KEY} is {@code 
true}.
+     */
+    public void install() throws Exception
+    {
+        String failureMessage = null;
+        Throwable t = null;
+        try
+        {
+            if (JREProvider.class.getName().equals(getProviderClassAsString()))
+            {
+                logger.info(format("Installation of a crypto provider was 
skipped as %s was used.", JREProvider.class.getName()));
+                return;
+            }
+
+            FBUtilities.classForName(getProviderClassAsString(), "crypto 
provider");
+
+            String providerName = getProviderName();
+            int providerPosition = getProviderPosition(providerName);
+            if (providerPosition > 0)
+            {
+                if (providerPosition == 1)
+                {
+                    logger.info("{} was already installed on position {}.", 
providerName, providerPosition);
+                }
+                else if (failOnMissingProvider)
+                {
+                    throw new IllegalStateException(String.format("%s was 
already installed on position %s.", providerName, providerPosition));
+                }
+                else
+                {
+                    logger.warn("{} was already installed on position {}. 
Check the configuration of " +
+                                "JRE and either remove the provider from 
java.security or do not install this provider " +
+                                "by Cassandra.", providerName, 
providerPosition);
+                    return;
+                }
+            }
+            else
+            {
+                Runnable r = installator();
+                if (r == null)
+                    throw new IllegalStateException("Installator runnable can 
not be null!");
+                else
+                    r.run();
+            }
+
+            if (isHealthyInstallation())
+                logger.info("{} health check OK.", getProviderName());
+            else
+                failureMessage = format("%s has not passed the health check. " 
+
+                                        "Check node's architecture (`uname 
-m`) is supported, see lib/<arch> subdirectories. " +
+                                        "The correct architecture-specific 
library for %s needs to be on the classpath. ",
+                                        getProviderName(),
+                                        getProviderClassAsString());
+        }
+        catch (ConfigurationException ex)
+        {
+            failureMessage = getProviderClassAsString() + " is not on the 
class path! " +
+                             "Check node's architecture (`uname -m`) is 
supported, see lib/<arch> subdirectories. " +
+                             "The correct architecture-specific library for 
needs to be on the classpath.";
+        }
+        catch (Throwable ex)
+        {
+            failureMessage = format("The installation of %s was not 
successful, reason: %s",
+                                    getProviderClassAsString(), 
ex.getMessage());
+            t = ex;
+        }
+
+        if (failureMessage != null)
+        {
+            // To be sure there is not any leftover, proactively remove this 
provider in case of any failure.
+            // This method returns silently if the provider is not installed 
or if name is null.
+            try
+            {
+                uninstall();
+            }
+            catch (Throwable throwable)
+            {
+                logger.warn("Uninstallation of {} failed", getProviderName(), 
throwable);
+            }
+
+            if (failOnMissingProvider)
+                throw new ConfigurationException(failureMessage, t);
+            else
+                logger.warn(failureMessage);
+        }
+    }
+
+    /**
+     * Uninstalls this crypto provider of name {@link #getProviderName()}
+     *
+     * @see Security#removeProvider(String)
+     */
+    public void uninstall()
+    {
+        Security.removeProvider(getProviderName());
+    }
+
+    private int getProviderPosition(String providerName)
+    {
+        Provider[] providers = Security.getProviders();
+
+        for (int i = 0; i < providers.length; i++)
+        {
+            if (providers[i].getName().equals(providerName))
+            {
+                return i + 1;
+            }
+        }
+
+        return -1;
+    }
+}
diff --git a/src/java/org/apache/cassandra/security/DefaultCryptoProvider.java 
b/src/java/org/apache/cassandra/security/DefaultCryptoProvider.java
new file mode 100644
index 0000000000..ac7ef2c652
--- /dev/null
+++ b/src/java/org/apache/cassandra/security/DefaultCryptoProvider.java
@@ -0,0 +1,67 @@
+/*
+ * 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.Map;
+import javax.crypto.Cipher;
+
+import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
+
+/**
+ * Default crypto provider tries to install AmazonCorrettoCryptoProvider.
+ * <p>
+ * The implementation falls back to in-built crypto provider in JRE if the 
installation
+ * is not successful.
+ */
+public class DefaultCryptoProvider extends AbstractCryptoProvider
+{
+    public DefaultCryptoProvider(Map<String, String> args)
+    {
+        super(args);
+    }
+
+    @Override
+    public String getProviderName()
+    {
+        return "AmazonCorrettoCryptoProvider";
+    }
+
+    @Override
+    public String getProviderClassAsString()
+    {
+        return 
"com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider";
+    }
+
+    @Override
+    public Runnable installator()
+    {
+        return AmazonCorrettoCryptoProvider::install;
+    }
+
+    @Override
+    public boolean isHealthyInstallation() throws Exception
+    {
+        if 
(!getProviderName().equals(Cipher.getInstance("AES/GCM/NoPadding").getProvider().getName()))
+            return false;
+
+        AmazonCorrettoCryptoProvider.INSTANCE.assertHealthy();
+
+        return true;
+    }
+}
diff --git a/src/java/org/apache/cassandra/security/JREProvider.java 
b/src/java/org/apache/cassandra/security/JREProvider.java
new file mode 100644
index 0000000000..0cd61a0d9c
--- /dev/null
+++ b/src/java/org/apache/cassandra/security/JREProvider.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Map;
+
+/**
+ * Crypto provider which does nothing. Handy for situations when a user
+ * wants to completely bypass crypto provider installation.
+ */
+public class JREProvider extends AbstractCryptoProvider
+{
+    public JREProvider(Map<String, String> properties)
+    {
+        super(properties);
+    }
+
+    @Override
+    public String getProviderName()
+    {
+        return JREProvider.class.getSimpleName();
+    }
+
+    @Override
+    public String getProviderClassAsString()
+    {
+        return JREProvider.class.getName();
+    }
+
+    @Override
+    protected Runnable installator()
+    {
+        return () -> {};
+    }
+
+    @Override
+    protected boolean isHealthyInstallation() throws Exception
+    {
+        return true;
+    }
+}
diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java 
b/src/java/org/apache/cassandra/utils/FBUtilities.java
index 6b2ddb0f86..a39b1bb22e 100644
--- a/src/java/org/apache/cassandra/utils/FBUtilities.java
+++ b/src/java/org/apache/cassandra/utils/FBUtilities.java
@@ -87,6 +87,7 @@ import org.apache.cassandra.io.util.DataOutputBufferFixed;
 import org.apache.cassandra.io.util.File;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.security.AbstractCryptoProvider;
 import org.apache.cassandra.security.ISslContextFactory;
 import org.apache.cassandra.utils.concurrent.FutureCombiner;
 import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
@@ -702,6 +703,26 @@ public class FBUtilities
         }
     }
 
+    public static AbstractCryptoProvider newCryptoProvider(String className, 
Map<String, String> parameters) throws ConfigurationException
+    {
+        try
+        {
+            if (!className.contains("."))
+                className = "org.apache.cassandra.security." + className;
+
+            Class<?> cryptoProviderClass = FBUtilities.classForName(className, 
"crypto provider class");
+            return (AbstractCryptoProvider) 
cryptoProviderClass.getConstructor(Map.class).newInstance(Collections.unmodifiableMap(parameters));
+        }
+        catch (Exception e)
+        {
+            // no need to wrap it in another ConfgurationException if 
FBUtilities.classForName might throw it
+            if (e instanceof ConfigurationException)
+                throw (ConfigurationException) e;
+            else
+                throw new ConfigurationException(String.format("Unable to 
create an instance of crypto provider for %s", className), e);
+        }
+    }
+
     /**
      * @return The Class for the given name.
      * @param classname Fully qualified classname.
diff --git 
a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java 
b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
index 590df6264e..6ae31794f4 100644
--- a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
+++ b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
@@ -923,6 +923,8 @@ public class Instance extends IsolatedExecutor implements 
IInvokableInstance
             
             error = parallelRun(error, executor, this::stopJmx);
 
+            error = parallelRun(error, executor, () -> 
DatabaseDescriptor.getCryptoProvider().uninstall());
+
             // Make sure any shutdown hooks registered for DeleteOnExit are 
released to prevent
             // references to the instance class loaders from being held
             if (graceful)
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/CryptoProviderTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/CryptoProviderTest.java
new file mode 100644
index 0000000000..f1e6428f08
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/CryptoProviderTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.distributed.test;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import org.apache.cassandra.distributed.Cluster;
+import 
org.apache.cassandra.distributed.api.IIsolatedExecutor.SerializableCallable;
+import org.apache.cassandra.security.DefaultCryptoProvider;
+import org.apache.cassandra.security.JREProvider;
+
+import static java.lang.String.format;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CryptoProviderTest extends TestBaseImpl
+{
+    @Before
+    public void beforeTest()
+    {
+        assertNotInstalledDefaultProvider();
+    }
+
+    @Test
+    public void testDefaultCryptoProvider() throws Throwable
+    {
+        try (Cluster cluster = init(Cluster.build(1)
+                                           .withConfig(config -> 
config.set("crypto_provider",
+                                                                            
new HashMap<>()
+                                                                            {{
+                                                                               
 put("class_name", DefaultCryptoProvider.class.getName());
+                                                                            
}}))
+                                           .start()))
+        {
+            assertTrue("Amazon Corretto Crypto Provider should be installed!",
+                       
cluster.get(1).callOnInstance((SerializableCallable<Boolean>) () -> {
+                           Provider provider = Security.getProviders()[0];
+                           return provider != null && 
AmazonCorrettoCryptoProvider.PROVIDER_NAME.equals(provider.getName());
+                       }));
+        }
+        finally
+        {
+            
Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        }
+    }
+
+    @Test
+    public void testUsingJREProvider() throws Throwable
+    {
+        try (Cluster cluster = init(Cluster.build(1)
+                                           .withConfig(config -> 
config.set("crypto_provider",
+                                                                            
new HashMap<>()
+                                                                            {{
+                                                                               
 put("class_name", JREProvider.class.getName());
+                                                                            
}}))
+                                           .start()))
+        {
+            assertTrue("Amazon Corretto Crypto Provider should not be 
installed!",
+                       
cluster.get(1).callOnInstance((SerializableCallable<Boolean>) () -> {
+                           Provider provider = Security.getProviders()[0];
+                           return provider != null && 
!AmazonCorrettoCryptoProvider.PROVIDER_NAME.equals(provider.getName());
+                       }));
+        }
+        finally
+        {
+            
Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        }
+    }
+
+    @Test
+    public void testUsingNoProviderUsesJREProvider() throws Throwable
+    {
+        // crypto_provider = null will be in Descriptor if it is not set in 
yaml (e.g. nodes being upgraded
+        // with the old cassandra.yaml, or if it is commented out
+        try (Cluster cluster = init(Cluster.build(1).start()))
+        {
+            assertTrue("Amazon Corretto Crypto Provider should not be 
installed!",
+                       
cluster.get(1).callOnInstance((SerializableCallable<Boolean>) () -> {
+                           Provider provider = Security.getProviders()[0];
+                           return provider != null && 
!AmazonCorrettoCryptoProvider.PROVIDER_NAME.equals(provider.getName());
+                       }));
+        }
+        finally
+        {
+            
Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        }
+    }
+
+    @Test
+    public void testFailedDefaultProviderInstallationShouldResumeStartup() 
throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1)
+                                        .withInstanceInitializer(BB::install)
+                                        .withConfig(config -> 
config.set("crypto_provider",
+                                                                         new 
HashMap<>()
+                                                                         {{
+                                                                             
put("class_name", DefaultCryptoProvider.class.getName());
+                                                                         }}))
+                                        .createWithoutStarting())
+        {
+            cluster.get(1).startup();
+
+            assertTrue("Amazon Corretto Crypto Provider should not be 
installed!",
+                       
cluster.get(1).callOnInstance((SerializableCallable<Boolean>) () -> {
+                           Provider provider = Security.getProviders()[0];
+                           return provider != null && 
!AmazonCorrettoCryptoProvider.PROVIDER_NAME.equals(provider.getName());
+                       }));
+        }
+        catch (Exception ex)
+        {
+            fail("Startup should not fail! It should fallback to in-built 
providers and continue to boot");
+        }
+        finally
+        {
+            
Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        }
+    }
+
+    @Test
+    public void 
testFailedDefaultProviderInstallationShouldFailStartupOnFailOnMissingProperty() 
throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1)
+                                        .withInstanceInitializer(BB::install)
+                                        .withConfig(config -> 
config.set("crypto_provider",
+                                                                         new 
HashMap<>()
+                                                                         {{
+                                                                             
put("class_name", DefaultCryptoProvider.class.getName());
+                                                                             
put("parameters", new HashMap<>()
+                                                                             {{
+                                                                               
  put("fail_on_missing_provider", "true");
+                                                                             
}});
+                                                                         }}))
+                                        .createWithoutStarting())
+        {
+            cluster.get(1).startup();
+
+            fail("Startup should fail! It should not fallback to in-built 
providers and continue to boot " +
+                 "as fail_on_missing_provider is set to true");
+        }
+        catch (Exception ex)
+        {
+            assertEquals(format("The installation of %s was not successful, 
reason: exception from test", AmazonCorrettoCryptoProvider.class.getName()),
+                         ex.getMessage());
+
+            assertNotInstalledDefaultProvider();
+        }
+        finally
+        {
+            
Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        }
+    }
+
+    public static void assertNotInstalledDefaultProvider()
+    {
+        for (Provider provider : Security.getProviders())
+            assertNotEquals(AmazonCorrettoCryptoProvider.PROVIDER_NAME, 
provider.getName());
+    }
+
+    public static class BB
+    {
+        public static void install(ClassLoader cl, Integer num)
+        {
+            new ByteBuddy().redefine(DefaultCryptoProvider.class)
+                           .method(named("installator"))
+                           .intercept(MethodDelegation.to(BB.class))
+                           .make()
+                           .load(cl, ClassLoadingStrategy.Default.INJECTION);
+        }
+
+        public static Runnable installator()
+        {
+            throw new RuntimeException("exception from test");
+        }
+    }
+}
diff --git 
a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java 
b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
index 8d5ff9aca4..4b1890ffff 100644
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
@@ -263,6 +263,7 @@ public class DatabaseDescriptorRefTest
     "org.apache.cassandra.security.ISslContextFactory",
     "org.apache.cassandra.security.SSLFactory",
     "org.apache.cassandra.service.CacheService$CacheType",
+    "org.apache.cassandra.security.AbstractCryptoProvider",
     "org.apache.cassandra.transport.ProtocolException",
     "org.apache.cassandra.utils.Closeable",
     "org.apache.cassandra.utils.CloseableIterator",
diff --git a/test/unit/org/apache/cassandra/security/CryptoProviderTest.java 
b/test/unit/org/apache/cassandra/security/CryptoProviderTest.java
new file mode 100644
index 0000000000..7bb4cbb893
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/CryptoProviderTest.java
@@ -0,0 +1,343 @@
+/*
+ * 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.security.Provider;
+import java.security.Security;
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.ParameterizedClass;
+import org.apache.cassandra.distributed.shared.WithProperties;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.utils.FBUtilities;
+import org.mockito.MockedStatic;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static java.lang.String.format;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.CRYPTO_PROVIDER_CLASS_NAME;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.FAIL_ON_MISSING_CRYPTO_PROVIDER;
+import static 
org.apache.cassandra.config.CassandraRelevantProperties.TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION;
+import static 
org.apache.cassandra.security.AbstractCryptoProvider.FAIL_ON_MISSING_PROVIDER_KEY;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.spy;
+
+public class CryptoProviderTest
+{
+    @BeforeClass
+    public static void beforeClass()
+    {
+        TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION.setBoolean(true);
+        DatabaseDescriptor.daemonInitialization();
+        TEST_SKIP_CRYPTO_PROVIDER_INSTALLATION.setBoolean(false);
+    }
+
+    @Before
+    public void beforeTest()
+    {
+        // be sure it is uninstalled / reset
+        DatabaseDescriptor.getRawConfig().crypto_provider = null;
+        DatabaseDescriptor.setCryptoProvider(null);
+        Security.removeProvider(AmazonCorrettoCryptoProvider.PROVIDER_NAME);
+        assertNotInstalledDefaultProvider();
+    }
+
+    public static void assertNotInstalledDefaultProvider()
+    {
+        for (Provider provider : Security.getProviders())
+            assertNotEquals(AmazonCorrettoCryptoProvider.PROVIDER_NAME, 
provider.getName());
+    }
+
+    @Test
+    public void testCryptoProviderClassSystemProperty()
+    {
+        try (WithProperties properties = new 
WithProperties().set(CRYPTO_PROVIDER_CLASS_NAME, 
TestJREProvider.class.getName()))
+        {
+            DatabaseDescriptor.applyCryptoProvider();
+            assertEquals(TestJREProvider.class.getSimpleName(), 
DatabaseDescriptor.getCryptoProvider().getProviderName());
+        }
+    }
+
+    @Test
+    public void testNoCryptoProviderInstallationUseJREProvider()
+    {
+        DatabaseDescriptor.applyCryptoProvider();
+        assertEquals("JREProvider", 
DatabaseDescriptor.getCryptoProvider().getProviderName());
+    }
+
+    @Test
+    public void testCryptoProviderInstallationWithNullParameters()
+    {
+        DatabaseDescriptor.getRawConfig().crypto_provider = new 
ParameterizedClass(TestJREProvider.class.getName(), null);
+        DatabaseDescriptor.applyCryptoProvider();
+
+        AbstractCryptoProvider cryptoProvider = 
DatabaseDescriptor.getCryptoProvider();
+        
assertThat(cryptoProvider.getProviderName()).isEqualTo(TestJREProvider.class.getSimpleName());
+        assertThat(cryptoProvider.getProperties()).isNotNull()
+                                                  .isNotEmpty()
+                                                  .hasSize(1)
+                                                  
.containsKeys("fail_on_missing_provider")
+                                                  .containsValues("false");
+    }
+
+    @Test
+    public void testCryptoProviderInstallationWithEmptyParameters()
+    {
+        DatabaseDescriptor.getRawConfig().crypto_provider = new 
ParameterizedClass(TestJREProvider.class.getName(), of());
+        DatabaseDescriptor.applyCryptoProvider();
+
+        AbstractCryptoProvider cryptoProvider = 
DatabaseDescriptor.getCryptoProvider();
+        
assertThat(cryptoProvider.getProviderName()).isEqualTo(TestJREProvider.class.getSimpleName());
+        assertThat(cryptoProvider.getProperties()).isNotNull()
+                                                  .isNotEmpty()
+                                                  .hasSize(1)
+                                                  
.containsKeys("fail_on_missing_provider")
+                                                  .containsValues("false");
+    }
+
+    @Test
+    public void testCryptoProviderInstallationWithNotEmptyParameters()
+    {
+        DatabaseDescriptor.getRawConfig().crypto_provider = new 
ParameterizedClass(TestJREProvider.class.getName(),
+                                                                               
    of("k1", "v1", "k2", "v2"));
+        DatabaseDescriptor.applyCryptoProvider();
+
+        AbstractCryptoProvider cryptoProvider = 
DatabaseDescriptor.getCryptoProvider();
+        
assertThat(cryptoProvider.getProviderName()).isEqualTo(TestJREProvider.class.getSimpleName());
+        assertThat(cryptoProvider.getProperties()).isNotNull()
+                                                  .isNotEmpty()
+                                                  .hasSize(3)
+                                                  .containsKeys("k1", "k2", 
"fail_on_missing_provider")
+                                                  .containsValues("v1", "v2", 
"false");
+    }
+
+    @Test
+    public void testCryptoProviderInstallationWithSimpleClassName()
+    {
+        DatabaseDescriptor.getRawConfig().crypto_provider = new 
ParameterizedClass(TestJREProvider.class.getSimpleName(), null);
+        DatabaseDescriptor.applyCryptoProvider();
+
+        AbstractCryptoProvider cryptoProvider = 
DatabaseDescriptor.getCryptoProvider();
+        
assertThat(cryptoProvider.getProviderName()).isEqualTo(TestJREProvider.class.getSimpleName());
+        assertThat(cryptoProvider.getProperties()).isNotNull()
+                                                  .isNotEmpty()
+                                                  .hasSize(1)
+                                                  
.containsKeys("fail_on_missing_provider")
+                                                  .containsValues("false");
+    }
+
+    @Test
+    public void testUnableToCreateDefaultCryptoProvider()
+    {
+        try (MockedStatic<FBUtilities> fbUtilitiesMock = 
mockStatic(FBUtilities.class))
+        {
+            DatabaseDescriptor.getRawConfig().crypto_provider = new 
ParameterizedClass(DefaultCryptoProvider.class.getName(),
+                                                                               
        of("k1", "v1", "k2", "v2"));
+
+            fbUtilitiesMock.when(() -> 
FBUtilities.classForName(DefaultCryptoProvider.class.getName(), "crypto 
provider class"))
+                           .thenThrow(new RuntimeException("exception from 
test"));
+
+            fbUtilitiesMock.when(() -> 
FBUtilities.newCryptoProvider(anyString(), anyMap())).thenCallRealMethod();
+
+            assertThatThrownBy(DatabaseDescriptor::applyCryptoProvider)
+            .isInstanceOf(ConfigurationException.class)
+            .hasCauseInstanceOf(RuntimeException.class)
+            .hasMessage("Unable to create an instance of crypto provider for " 
+ DefaultCryptoProvider.class.getName())
+            .hasRootCauseMessage("exception from test");
+
+            assertNotInstalledDefaultProvider();
+        }
+    }
+
+    @Test
+    public void testFailOnMissingProviderSystemProperty()
+    {
+        try (WithProperties properties = new 
WithProperties().set(FAIL_ON_MISSING_CRYPTO_PROVIDER, "true")
+                                                             
.set(CRYPTO_PROVIDER_CLASS_NAME, InvalidCryptoProvider.class.getName()))
+        {
+            assertThatExceptionOfType(ConfigurationException.class)
+            .isThrownBy(DatabaseDescriptor::applyCryptoProvider)
+            .withMessage("some.package.non.existing.ClassName is not on the 
class path! Check node's architecture " +
+                         "(`uname -m`) is supported, see lib/<arch> 
subdirectories. The correct architecture-specific " +
+                         "library for needs to be on the classpath.");
+        }
+    }
+
+    @Test
+    public void testCryptoProviderInstallation() throws Exception
+    {
+        AbstractCryptoProvider provider = new DefaultCryptoProvider(new 
HashMap<>());
+        assertFalse(provider.failOnMissingProvider);
+
+        Provider originalProvider = Security.getProviders()[0];
+
+        provider.install();
+        assertTrue(provider.isHealthyInstallation());
+        Provider installedProvider = Security.getProviders()[0];
+        assertEquals(installedProvider.getName(), provider.getProviderName());
+
+        provider.uninstall();
+        Provider currentProvider = Security.getProviders()[0];
+        assertNotEquals(currentProvider.getName(), 
installedProvider.getName());
+        assertEquals(originalProvider.getName(), currentProvider.getName());
+    }
+
+    @Test
+    public void testInvalidProviderInstallator()
+    {
+        AbstractCryptoProvider spiedProvider = spy(new 
DefaultCryptoProvider(of(FAIL_ON_MISSING_PROVIDER_KEY, "true")));
+
+        Runnable installator = () ->
+        {
+            throw new RuntimeException("invalid installator");
+        };
+
+        doReturn(installator).when(spiedProvider).installator();
+
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(spiedProvider::install)
+        .withRootCauseInstanceOf(RuntimeException.class)
+        .withMessage("The installation of %s was not successful, reason: 
invalid installator", spiedProvider.getProviderClassAsString());
+    }
+
+    @Test
+    public void testNullInstallatorThrowsException()
+    {
+        AbstractCryptoProvider spiedProvider = spy(new 
DefaultCryptoProvider(of(FAIL_ON_MISSING_PROVIDER_KEY, "true")));
+
+        doReturn(null).when(spiedProvider).installator();
+
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(spiedProvider::install)
+        .withRootCauseInstanceOf(RuntimeException.class)
+        .withMessage("The installation of %s was not successful, reason: 
Installator runnable can not be null!", 
spiedProvider.getProviderClassAsString());
+    }
+
+    @Test
+    public void testProviderHealthcheckerReturningFalse() throws Exception
+    {
+        AbstractCryptoProvider spiedProvider = spy(new 
DefaultCryptoProvider(of(FAIL_ON_MISSING_PROVIDER_KEY, "true")));
+
+        doReturn(false).when(spiedProvider).isHealthyInstallation();
+
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(spiedProvider::install)
+        .withCause(null)
+        .withMessage(format("%s has not passed the health check. " +
+                            "Check node's architecture (`uname -m`) is 
supported, see lib/<arch> subdirectories. " +
+                            "The correct architecture-specific library for %s 
needs to be on the classpath. ",
+                            spiedProvider.getProviderName(),
+                            spiedProvider.getProviderClassAsString()));
+    }
+
+    @Test
+    public void testHealthcheckerThrowingException() throws Exception
+    {
+        AbstractCryptoProvider spiedProvider = spy(new 
DefaultCryptoProvider(of(FAIL_ON_MISSING_PROVIDER_KEY, "true")));
+
+        Throwable t = new RuntimeException("error in health checker");
+        doThrow(t).when(spiedProvider).isHealthyInstallation();
+
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(spiedProvider::install)
+        .withCauseInstanceOf(RuntimeException.class)
+        .withMessage(format("The installation of %s was not successful, 
reason: %s",
+                            spiedProvider.getProviderClassAsString(), 
t.getMessage()));
+    }
+
+    @Test
+    public void testProviderNotOnClassPathWithPropertyInYaml() throws Exception
+    {
+        InvalidCryptoProvider cryptoProvider = new 
InvalidCryptoProvider(of(FAIL_ON_MISSING_PROVIDER_KEY, "true"));
+
+        assertThatExceptionOfType(ConfigurationException.class)
+        .isThrownBy(cryptoProvider::install)
+        .withMessage("some.package.non.existing.ClassName is not on the class 
path! Check node's architecture " +
+                     "(`uname -m`) is supported, see lib/<arch> 
subdirectories. " +
+                     "The correct architecture-specific library for needs to 
be on the classpath.");
+    }
+
+    @Test
+    public void testProviderNotOnClassPathWithSystemProperty()
+    {
+        try (WithProperties properties = new 
WithProperties().set(FAIL_ON_MISSING_CRYPTO_PROVIDER, "true"))
+        {
+            InvalidCryptoProvider cryptoProvider = new 
InvalidCryptoProvider(of());
+
+            assertThatExceptionOfType(ConfigurationException.class)
+            .isThrownBy(cryptoProvider::install)
+            .withMessage("some.package.non.existing.ClassName is not on the 
class path! Check node's architecture " +
+                         "(`uname -m`) is supported, see lib/<arch> 
subdirectories. The correct architecture-specific " +
+                         "library for needs to be on the classpath.");
+        }
+    }
+
+    @Test
+    public void testProviderInstallsJustOnce() throws Exception
+    {
+        Provider[] originalProviders = Security.getProviders();
+        int originalProvidersCount = originalProviders.length;
+        Provider originalProvider = Security.getProviders()[0];
+
+        AbstractCryptoProvider provider = new DefaultCryptoProvider(new 
HashMap<>());
+        provider.install();
+
+        assertEquals(provider.getProviderName(), 
Security.getProviders()[0].getName());
+        assertEquals(originalProvidersCount + 1, 
Security.getProviders().length);
+
+        // install one more time -> it will do nothing
+
+        provider.install();
+
+        assertEquals(provider.getProviderName(), 
Security.getProviders()[0].getName());
+        assertEquals(originalProvidersCount + 1, 
Security.getProviders().length);
+
+        provider.uninstall();
+
+        assertEquals(originalProvider.getName(), 
Security.getProviders()[0].getName());
+        assertEquals(originalProvidersCount, Security.getProviders().length);
+    }
+
+    @Test
+    public void testInstallationOfIJREProvider() throws Exception
+    {
+        String originalProvider = Security.getProviders()[0].getName();
+
+        JREProvider jreProvider = new JREProvider(of());
+        jreProvider.install();
+
+        assertEquals(originalProvider, Security.getProviders()[0].getName());
+    }
+}
diff --git a/test/unit/org/apache/cassandra/security/InvalidCryptoProvider.java 
b/test/unit/org/apache/cassandra/security/InvalidCryptoProvider.java
new file mode 100644
index 0000000000..47aae8ea92
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/InvalidCryptoProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Map;
+
+public class InvalidCryptoProvider extends AbstractCryptoProvider
+{
+    public InvalidCryptoProvider(Map<String, String> properties)
+    {
+        super(properties);
+    }
+
+    @Override
+    public String getProviderName()
+    {
+        return null;
+    }
+
+    @Override
+    public String getProviderClassAsString()
+    {
+        return "some.package.non.existing.ClassName";
+    }
+
+    @Override
+    protected Runnable installator()
+    {
+        return () -> {};
+    }
+
+    @Override
+    protected boolean isHealthyInstallation() throws Exception
+    {
+        return false;
+    }
+}
diff --git a/test/unit/org/apache/cassandra/security/TestJREProvider.java 
b/test/unit/org/apache/cassandra/security/TestJREProvider.java
new file mode 100644
index 0000000000..d6f700b965
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/TestJREProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.Map;
+
+//  to be public because it is going to be instantiated by reflection in 
FBUtilties
+public class TestJREProvider extends JREProvider
+{
+    public TestJREProvider(Map<String, String> properties)
+    {
+        super(properties);
+    }
+
+    @Override
+    public String getProviderName()
+    {
+        return TestJREProvider.class.getSimpleName();
+    }
+
+    @Override
+    public String getProviderClassAsString()
+    {
+        return TestJREProvider.class.getName();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to