Skip to content

Commit 668a44e

Browse files
authored
Merge branch 'trunk' into CASSANDRA-19480
2 parents b273100 + 6901018 commit 668a44e

13 files changed

Lines changed: 708 additions & 490 deletions

File tree

cassandra-analytics-integration-framework/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ dependencies {
7575
exclude group: 'junit', module: 'junit'
7676
}
7777
implementation("io.vertx:vertx-web-client:${project.vertxVersion}")
78-
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.14.3'
78+
implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.14.3')
79+
80+
// Bouncycastle dependencies for test certificate provisioning
81+
implementation(group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.78')
82+
implementation(group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: '1.78')
7983

8084
testImplementation(platform("org.junit:junit-bom:${project.junitVersion}"))
8185
testImplementation('org.junit.jupiter:junit-jupiter')

cassandra-analytics-integration-framework/src/main/java/org/apache/cassandra/distributed/impl/CassandraCluster.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public class CassandraCluster<I extends IInstance> implements IClusterExtension<
6868
// java.lang.IllegalStateException: Can't load <CLASS>. Instance class loader is already closed.
6969
return className.equals("org.apache.cassandra.utils.concurrent.Ref$OnLeak")
7070
|| className.startsWith("org.apache.cassandra.metrics.RestorableMeter")
71+
|| className.equals("org.apache.logging.slf4j.EventDataConverter")
7172
|| (className.startsWith("org.apache.cassandra.analytics.") && className.contains("BBHelper"));
7273
};
7374

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.sidecar.testing;
20+
21+
import java.nio.file.Path;
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
import org.apache.cassandra.testing.utils.tls.CertificateBuilder;
31+
import org.apache.cassandra.testing.utils.tls.CertificateBundle;
32+
33+
/**
34+
* A class that encapsulates testing with Mutual TLS.
35+
*/
36+
public class MtlsTestHelper
37+
{
38+
private static final Logger LOGGER = LoggerFactory.getLogger(MtlsTestHelper.class);
39+
public static final char[] EMPTY_PASSWORD = new char[0];
40+
public static final String EMPTY_PASSWORD_STRING = "";
41+
/**
42+
* A system property that can enable / disable testing with Mutual TLS
43+
*/
44+
public static final String CASSANDRA_INTEGRATION_TEST_ENABLE_MTLS = "cassandra.integration.sidecar.test.enable_mtls";
45+
private final boolean enableMtlsForTesting;
46+
CertificateBundle certificateAuthority;
47+
Path truststorePath;
48+
Path serverKeyStorePath;
49+
Path clientKeyStorePath;
50+
51+
public MtlsTestHelper(Path secretsPath) throws Exception
52+
{
53+
this(secretsPath, System.getProperty(CASSANDRA_INTEGRATION_TEST_ENABLE_MTLS, "false").equals("true"));
54+
}
55+
56+
public MtlsTestHelper(Path secretsPath, boolean enableMtlsForTesting) throws Exception
57+
{
58+
this.enableMtlsForTesting = enableMtlsForTesting;
59+
maybeInitializeSecrets(Objects.requireNonNull(secretsPath, "secretsPath cannot be null"));
60+
}
61+
62+
void maybeInitializeSecrets(Path secretsPath) throws Exception
63+
{
64+
if (!enableMtlsForTesting)
65+
{
66+
return;
67+
}
68+
69+
certificateAuthority =
70+
new CertificateBuilder().subject("CN=Apache Cassandra Root CA, OU=Certification Authority, O=Unknown, C=Unknown")
71+
.alias("fakerootca")
72+
.isCertificateAuthority(true)
73+
.buildSelfSigned();
74+
truststorePath = certificateAuthority.toTempKeyStorePath(secretsPath, EMPTY_PASSWORD, EMPTY_PASSWORD);
75+
76+
CertificateBuilder serverKeyStoreBuilder =
77+
new CertificateBuilder().subject("CN=Apache Cassandra, OU=mtls_test, O=Unknown, L=Unknown, ST=Unknown, C=Unknown")
78+
.addSanDnsName("localhost");
79+
// Add SANs for every potential hostname Sidecar will serve
80+
for (int i = 1; i <= 20; i++)
81+
{
82+
serverKeyStoreBuilder.addSanDnsName("localhost" + i);
83+
}
84+
85+
CertificateBundle serverKeyStore = serverKeyStoreBuilder.buildIssuedBy(certificateAuthority);
86+
serverKeyStorePath = serverKeyStore.toTempKeyStorePath(secretsPath, EMPTY_PASSWORD, EMPTY_PASSWORD);
87+
CertificateBundle clientKeyStore = new CertificateBuilder().subject("CN=Apache Cassandra, OU=mtls_test, O=Unknown, L=Unknown, ST=Unknown, C=Unknown")
88+
.addSanDnsName("localhost")
89+
.buildIssuedBy(certificateAuthority);
90+
clientKeyStorePath = clientKeyStore.toTempKeyStorePath(secretsPath, EMPTY_PASSWORD, EMPTY_PASSWORD);
91+
}
92+
93+
public boolean isEnabled()
94+
{
95+
return enableMtlsForTesting;
96+
}
97+
98+
public String trustStorePath()
99+
{
100+
return truststorePath.toString();
101+
}
102+
103+
public String trustStorePassword()
104+
{
105+
return EMPTY_PASSWORD_STRING;
106+
}
107+
108+
public String trustStoreType()
109+
{
110+
return "PKCS12";
111+
}
112+
113+
public String serverKeyStorePath()
114+
{
115+
return serverKeyStorePath.toString();
116+
}
117+
118+
public String serverKeyStorePassword()
119+
{
120+
return EMPTY_PASSWORD_STRING;
121+
}
122+
123+
public String serverKeyStoreType()
124+
{
125+
return "PKCS12";
126+
}
127+
128+
public Map<String, String> mtlOptionMap()
129+
{
130+
if (!isEnabled())
131+
{
132+
return Collections.emptyMap();
133+
}
134+
135+
LOGGER.info("Test mTLS certificate is enabled. "
136+
+ "Will use test keystore as truststore so the client will trust the server");
137+
Map<String, String> optionMap = new HashMap<>();
138+
optionMap.put("truststore_path", trustStorePath());
139+
optionMap.put("truststore_password", EMPTY_PASSWORD_STRING);
140+
optionMap.put("truststore_type", trustStoreType());
141+
optionMap.put("keystore_path", clientKeyStorePath.toString());
142+
optionMap.put("keystore_password", EMPTY_PASSWORD_STRING);
143+
optionMap.put("keystore_type", "PKCS12");
144+
return optionMap;
145+
}
146+
}

cassandra-analytics-integration-framework/src/main/java/org/apache/cassandra/sidecar/testing/SharedClusterIntegrationTestBase.java

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.junit.jupiter.api.BeforeAll;
4040
import org.junit.jupiter.api.TestInstance;
4141
import org.junit.jupiter.api.extension.ExtendWith;
42+
import org.junit.jupiter.api.io.TempDir;
4243
import org.slf4j.Logger;
4344
import org.slf4j.LoggerFactory;
4445

@@ -72,10 +73,14 @@
7273
import org.apache.cassandra.sidecar.common.utils.DriverUtils;
7374
import org.apache.cassandra.sidecar.common.utils.SidecarVersionProvider;
7475
import org.apache.cassandra.sidecar.config.JmxConfiguration;
76+
import org.apache.cassandra.sidecar.config.KeyStoreConfiguration;
7577
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
7678
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
79+
import org.apache.cassandra.sidecar.config.SslConfiguration;
80+
import org.apache.cassandra.sidecar.config.yaml.KeyStoreConfigurationImpl;
7781
import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
7882
import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
83+
import org.apache.cassandra.sidecar.config.yaml.SslConfigurationImpl;
7984
import org.apache.cassandra.sidecar.exceptions.ThrowableUtils;
8085
import org.apache.cassandra.sidecar.server.MainModule;
8186
import org.apache.cassandra.sidecar.server.Server;
@@ -87,6 +92,7 @@
8792
import org.apache.cassandra.testing.TestVersion;
8893
import org.apache.cassandra.testing.TestVersionSupplier;
8994

95+
import static org.apache.cassandra.sidecar.testing.MtlsTestHelper.CASSANDRA_INTEGRATION_TEST_ENABLE_MTLS;
9096
import static org.assertj.core.api.Assertions.assertThat;
9197

9298
/**
@@ -131,12 +137,16 @@ public abstract class SharedClusterIntegrationTestBase
131137
protected final Logger logger = LoggerFactory.getLogger(SharedClusterIntegrationTestBase.class);
132138
private static final int MAX_CLUSTER_PROVISION_RETRIES = 5;
133139

140+
@TempDir
141+
static Path secretsPath;
142+
134143
protected Vertx vertx;
135144
protected DnsResolver dnsResolver;
136145
protected IClusterExtension<? extends IInstance> cluster;
137146
protected Server server;
138147
protected Injector injector;
139148
protected TestVersion testVersion;
149+
protected MtlsTestHelper mtlsTestHelper;
140150
private IsolatedDTestClassLoaderWrapper classLoaderWrapper;
141151

142152
static
@@ -146,7 +156,7 @@ public abstract class SharedClusterIntegrationTestBase
146156
}
147157

148158
@BeforeAll
149-
protected void setup() throws InterruptedException
159+
protected void setup() throws Exception
150160
{
151161
Optional<TestVersion> maybeTestVersion = TestVersionSupplier.testVersions().findFirst();
152162
assertThat(maybeTestVersion).isPresent();
@@ -161,6 +171,7 @@ protected void setup() throws InterruptedException
161171
assertThat(cluster).isNotNull();
162172
afterClusterProvisioned();
163173
initializeSchemaForTest();
174+
mtlsTestHelper = new MtlsTestHelper(secretsPath);
164175
startSidecar(cluster);
165176
beforeTestStart();
166177
}
@@ -306,7 +317,8 @@ protected void createTestTable(QualifiedName name, String createTableStatement)
306317
protected void startSidecar(ICluster<? extends IInstance> cluster) throws InterruptedException
307318
{
308319
VertxTestContext context = new VertxTestContext();
309-
injector = Guice.createInjector(Modules.override(new MainModule()).with(new IntegrationTestModule(cluster, classLoaderWrapper)));
320+
AbstractModule testModule = new IntegrationTestModule(cluster, classLoaderWrapper, mtlsTestHelper);
321+
injector = Guice.createInjector(Modules.override(new MainModule()).with(testModule));
310322
dnsResolver = injector.getInstance(DnsResolver.class);
311323
vertx = injector.getInstance(Vertx.class);
312324
server = injector.getInstance(Server.class);
@@ -455,13 +467,18 @@ public static Cluster createDriverCluster(ICluster<? extends IInstance> dtest)
455467

456468
static class IntegrationTestModule extends AbstractModule
457469
{
470+
private static final Logger LOGGER = LoggerFactory.getLogger(IntegrationTestModule.class);
458471
private final ICluster<? extends IInstance> cluster;
459472
private final IsolatedDTestClassLoaderWrapper wrapper;
473+
private final MtlsTestHelper mtlsTestHelper;
460474

461-
IntegrationTestModule(ICluster<? extends IInstance> cluster, IsolatedDTestClassLoaderWrapper wrapper)
475+
IntegrationTestModule(ICluster<? extends IInstance> cluster,
476+
IsolatedDTestClassLoaderWrapper wrapper,
477+
MtlsTestHelper mtlsTestHelper)
462478
{
463479
this.cluster = cluster;
464480
this.wrapper = wrapper;
481+
this.mtlsTestHelper = mtlsTestHelper;
465482
}
466483

467484
@Provides
@@ -500,8 +517,39 @@ public SidecarConfiguration sidecarConfiguration()
500517
.host("0.0.0.0") // binds to all interfaces, potential security issue if left running for long
501518
.port(0) // let the test find an available port
502519
.build();
520+
521+
522+
SslConfiguration sslConfiguration = null;
523+
if (mtlsTestHelper.isEnabled())
524+
{
525+
LOGGER.info("Enabling test mTLS certificate/keystore.");
526+
527+
KeyStoreConfiguration truststoreConfiguration =
528+
new KeyStoreConfigurationImpl(mtlsTestHelper.trustStorePath(),
529+
mtlsTestHelper.trustStorePassword(),
530+
mtlsTestHelper.trustStoreType(),
531+
-1);
532+
533+
KeyStoreConfiguration keyStoreConfiguration =
534+
new KeyStoreConfigurationImpl(mtlsTestHelper.serverKeyStorePath(),
535+
mtlsTestHelper.serverKeyStorePassword(),
536+
mtlsTestHelper.serverKeyStoreType(),
537+
-1);
538+
539+
sslConfiguration = SslConfigurationImpl.builder()
540+
.enabled(true)
541+
.keystore(keyStoreConfiguration)
542+
.truststore(truststoreConfiguration)
543+
.build();
544+
}
545+
else
546+
{
547+
LOGGER.info("Not enabling mTLS for testing purposes. Set '{}' to 'true' if you would " +
548+
"like mTLS enabled.", CASSANDRA_INTEGRATION_TEST_ENABLE_MTLS);
549+
}
503550
return SidecarConfigurationImpl.builder()
504551
.serviceConfiguration(conf)
552+
.sslConfiguration(sslConfiguration)
505553
.build();
506554
}
507555

0 commit comments

Comments
 (0)