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 <[email protected]>
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 <[email protected]>
---
.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: [email protected]
For additional commands, e-mail: [email protected]