mirror of https://github.com/apache/cassandra
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 <smiklosovic@apache.org>
This commit is contained in:
parent
a9d1712aec
commit
6ab45971fc
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
7
NEWS.txt
7
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue