cassandra/test/unit/org/apache/cassandra/security/CryptoProviderTest.java

345 lines
15 KiB
Java

/*
* 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 com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
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 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.classForNameWithoutInitialization(DefaultCryptoProvider.class.getName(), "crypto provider class", AbstractCryptoProvider.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());
}
}