mirror of https://github.com/apache/cassandra
280 lines
13 KiB
Java
280 lines
13 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.auth;
|
|
|
|
import java.io.IOException;
|
|
import java.security.cert.Certificate;
|
|
import java.security.cert.CertificateException;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Map;
|
|
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
|
|
import org.apache.cassandra.config.Config;
|
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
|
import org.apache.cassandra.config.ParameterizedClass;
|
|
import org.apache.cassandra.exceptions.ConfigurationException;
|
|
import org.apache.cassandra.locator.InetAddressAndPort;
|
|
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
|
|
import org.apache.cassandra.utils.ClassLoadingTestSupport;
|
|
import org.apache.cassandra.utils.MBeanWrapper;
|
|
|
|
import static org.apache.cassandra.auth.AuthCache.MBEAN_NAME_BASE;
|
|
import static org.apache.cassandra.auth.AuthTestUtils.loadCertificateChain;
|
|
import static org.apache.cassandra.auth.IInternodeAuthenticator.InternodeConnectionDirection.INBOUND;
|
|
import static org.apache.cassandra.config.YamlConfigurationLoaderTest.load;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
/**
|
|
* Tests instantiation of various authenticators through AuthConfig, and the accessibility of the configured
|
|
* authenticators and role manager options through DatabaseDescriptor.
|
|
*/
|
|
public class AuthConfigTest
|
|
{
|
|
private static final String IDENTITIES_CACHE_MBEAN = MBEAN_NAME_BASE + MutualTlsAuthenticator.CACHE_NAME;
|
|
|
|
private static final String CREDENTIALS_CACHE_MBEAN = MBEAN_NAME_BASE + PasswordAuthenticator.CredentialsCacheMBean.CACHE_NAME;
|
|
|
|
@Before
|
|
public void setup()
|
|
{
|
|
AuthConfig.reset();
|
|
}
|
|
|
|
@After
|
|
public void teardown()
|
|
{
|
|
unregisterCaches();
|
|
}
|
|
|
|
@Test
|
|
public void testNewInstanceForMutualTlsInternodeAuthenticator() throws IOException, CertificateException
|
|
{
|
|
Config config = load("cassandra-mtls.yaml");
|
|
config.internode_authenticator.class_name = "org.apache.cassandra.auth.MutualTlsInternodeAuthenticator";
|
|
config.internode_authenticator.parameters = Collections.singletonMap("validator_class_name", "org.apache.cassandra.auth.SpiffeCertificateValidator");
|
|
DatabaseDescriptor.unsafeDaemonInitialization(()->config);
|
|
|
|
MutualTlsInternodeAuthenticator authenticator = ParameterizedClass.newInstance(config.internode_authenticator,
|
|
Arrays.asList("", "org.apache.cassandra.auth."));
|
|
assertNotNull(authenticator);
|
|
|
|
InetAddressAndPort address = InetAddressAndPort.getByName("127.0.0.1");
|
|
|
|
Certificate[] authorizedCertificates = loadCertificateChain("auth/SampleMtlsClientCertificate.pem");
|
|
assertTrue(authenticator.authenticate(address.getAddress(), address.getPort(), authorizedCertificates, INBOUND));
|
|
|
|
Certificate[] unauthorizedCertificates = loadCertificateChain("auth/SampleUnauthorizedMtlsClientCertificate.pem");
|
|
assertFalse(authenticator.authenticate(address.getAddress(), address.getPort(), unauthorizedCertificates, INBOUND));
|
|
unregisterCaches();
|
|
}
|
|
|
|
@Test
|
|
public void testNewInstanceForPasswordAuthenticator()
|
|
{
|
|
Config config = load("cassandra-passwordauth.yaml");
|
|
DatabaseDescriptor.unsafeDaemonInitialization(()->config);
|
|
|
|
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
|
assertNotNull(authenticator);
|
|
|
|
assertThat(DatabaseDescriptor.getAuthenticator(PasswordAuthenticator.class))
|
|
.isPresent()
|
|
.get()
|
|
.isSameAs(authenticator);
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsAuthenticator.class)).isEmpty();
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsWithPasswordFallbackAuthenticator.class)).isEmpty();
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(CassandraRoleManager.DEFAULT_SUPPORTED_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(PasswordAuthenticator.SUPPORTED_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(CassandraRoleManager.DEFAULT_ALTERABLE_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(PasswordAuthenticator.ALTERABLE_ROLE_OPTIONS));
|
|
}
|
|
|
|
@Test
|
|
public void testNewInstanceForMutualTlsWithPasswordFallbackAuthenticator()
|
|
{
|
|
Config config = load("cassandra-mtls.yaml");
|
|
config.authenticator.class_name = "org.apache.cassandra.auth.MutualTlsWithPasswordFallbackAuthenticator";
|
|
config.authenticator.parameters = Collections.singletonMap("validator_class_name", "org.apache.cassandra.auth.SpiffeCertificateValidator");
|
|
DatabaseDescriptor.unsafeDaemonInitialization(()->config);
|
|
|
|
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
|
assertNotNull(authenticator);
|
|
|
|
// MutualTlsWithPasswordFallbackAuthenticator is-a PasswordAuthenticator, so we expect getAuthenticator to
|
|
// return it when asked to return a PasswordAuthenticator.
|
|
assertThat(DatabaseDescriptor.getAuthenticator(PasswordAuthenticator.class))
|
|
.isPresent()
|
|
.get()
|
|
.isSameAs(authenticator);
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsAuthenticator.class)).isEmpty();
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsWithPasswordFallbackAuthenticator.class))
|
|
.isPresent()
|
|
.get()
|
|
.isSameAs(authenticator);
|
|
|
|
// Similarly, we expect the same role options as for PasswordAuthenticator.
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(CassandraRoleManager.DEFAULT_SUPPORTED_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(PasswordAuthenticator.SUPPORTED_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(CassandraRoleManager.DEFAULT_ALTERABLE_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(PasswordAuthenticator.ALTERABLE_ROLE_OPTIONS));
|
|
}
|
|
|
|
@Test
|
|
public void testNewInstanceForMutualTlsAuthenticator()
|
|
{
|
|
Config config = load("cassandra-mtls.yaml");
|
|
DatabaseDescriptor.unsafeDaemonInitialization(()->config);
|
|
|
|
IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
|
|
assertNotNull(authenticator);
|
|
|
|
assertThat(DatabaseDescriptor.getAuthenticator(PasswordAuthenticator.class)).isEmpty();
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsAuthenticator.class))
|
|
.isPresent()
|
|
.get()
|
|
.isSameAs(authenticator);
|
|
assertThat(DatabaseDescriptor.getAuthenticator(MutualTlsWithPasswordFallbackAuthenticator.class)).isEmpty();
|
|
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(CassandraRoleManager.DEFAULT_SUPPORTED_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().supportedOptions().containsAll(authenticator.getSupportedRoleOptions()));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(CassandraRoleManager.DEFAULT_ALTERABLE_ROLE_OPTIONS));
|
|
assertTrue(DatabaseDescriptor.getRoleManager().alterableOptions().containsAll(authenticator.getAlterableRoleOptions()));
|
|
}
|
|
|
|
@Test
|
|
public void testNewInstanceForMutualTlsDefaultRoleInitializer()
|
|
{
|
|
Config config = load("cassandra-mtls.yaml");
|
|
config.default_role_initializer = new ParameterizedClass("org.apache.cassandra.auth.MutualTlsDefaultRoleInitializer",
|
|
Map.of("role", "cassandra", "identity", "spiffe1"));
|
|
DatabaseDescriptor.unsafeDaemonInitialization(()->config);
|
|
|
|
assertThat(DatabaseDescriptor.getDefaultRoleInitializer()).isInstanceOf(MutualTlsDefaultRoleInitializer.class);
|
|
assertThat(DatabaseDescriptor.getDefaultRoleInitializer().defaultRoleName()).isEqualTo("cassandra");
|
|
}
|
|
|
|
@Test
|
|
public void testMutualTlsDefaultRoleInitializerRejectedWithIncompatibleAuthenticator()
|
|
{
|
|
Config config = load("cassandra-passwordauth.yaml");
|
|
config.default_role_initializer = new ParameterizedClass("org.apache.cassandra.auth.MutualTlsDefaultRoleInitializer",
|
|
Map.of("role", "cassandra", "identity", "spiffe1"));
|
|
assertThatThrownBy(() -> DatabaseDescriptor.unsafeDaemonInitialization(()->config))
|
|
.isInstanceOf(ConfigurationException.class)
|
|
.hasMessageContaining("creates a role with no password");
|
|
}
|
|
|
|
private static final String PROBE = ClassLoadingTestNonAssignable.class.getName();
|
|
|
|
private static Config baseConfig()
|
|
{
|
|
// Use the default (AllowAll) auth config so the non-probe auth components do not register JMX caches that
|
|
// would conflict across the per-field probe tests below.
|
|
Config config = load("cassandra.yaml");
|
|
DatabaseDescriptor.unsafeDaemonInitialization(() -> config);
|
|
return config;
|
|
}
|
|
|
|
private static void assertApplyAuthRejectsProbe()
|
|
{
|
|
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
|
|
AuthConfig.reset();
|
|
assertThatThrownBy(AuthConfig::applyAuth)
|
|
.isInstanceOf(ConfigurationException.class)
|
|
.hasMessageContaining("must extend or implement");
|
|
assertThat(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class)).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void testAuthenticatorWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.authenticator = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testAuthorizerWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.authorizer = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testRoleManagerWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.role_manager = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testDefaultRoleInitializerWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.default_role_initializer = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testInternodeAuthenticatorWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.internode_authenticator = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testNetworkAuthorizerWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.network_authorizer = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
@Test
|
|
public void testCidrAuthorizerWrongTypeRejectedWithoutInitializing()
|
|
{
|
|
Config config = baseConfig();
|
|
config.cidr_authorizer = new ParameterizedClass(PROBE, Collections.emptyMap());
|
|
assertApplyAuthRejectsProbe();
|
|
}
|
|
|
|
private void unregisterCaches()
|
|
{
|
|
safeUnregisterMbean(IDENTITIES_CACHE_MBEAN);
|
|
safeUnregisterMbean(CREDENTIALS_CACHE_MBEAN);
|
|
}
|
|
|
|
private void safeUnregisterMbean(String mbeanName)
|
|
{
|
|
if (MBeanWrapper.instance.isRegistered(mbeanName))
|
|
MBeanWrapper.instance.unregisterMBean(mbeanName);
|
|
}
|
|
}
|