Skip to content

Commit 9c520d6

Browse files
committed
Add SSL service connection support for Cassandra
See gh-41137
1 parent e26ccbe commit 9c520d6

File tree

6 files changed

+62
-46
lines changed

6 files changed

+62
-46
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java

+25-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,15 +17,12 @@
1717
package org.springframework.boot.autoconfigure.cassandra;
1818

1919
import java.io.IOException;
20-
import java.security.NoSuchAlgorithmException;
2120
import java.time.Duration;
2221
import java.util.Collections;
2322
import java.util.LinkedHashMap;
2423
import java.util.List;
2524
import java.util.Map;
2625

27-
import javax.net.ssl.SSLContext;
28-
2926
import com.datastax.oss.driver.api.core.CqlSession;
3027
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
3128
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
@@ -99,8 +96,8 @@ public class CassandraAutoConfiguration {
9996

10097
@Bean
10198
@ConditionalOnMissingBean(CassandraConnectionDetails.class)
102-
PropertiesCassandraConnectionDetails cassandraConnectionDetails() {
103-
return new PropertiesCassandraConnectionDetails(this.properties);
99+
PropertiesCassandraConnectionDetails cassandraConnectionDetails(ObjectProvider<SslBundles> sslBundles) {
100+
return new PropertiesCassandraConnectionDetails(this.properties, sslBundles.getIfAvailable());
104101
}
105102

106103
@Bean
@@ -115,10 +112,10 @@ public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
115112
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
116113
public CqlSessionBuilder cassandraSessionBuilder(DriverConfigLoader driverConfigLoader,
117114
CassandraConnectionDetails connectionDetails,
118-
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers, ObjectProvider<SslBundles> sslBundles) {
115+
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
119116
CqlSessionBuilder builder = CqlSession.builder().withConfigLoader(driverConfigLoader);
120117
configureAuthentication(builder, connectionDetails);
121-
configureSsl(builder, sslBundles.getIfAvailable());
118+
configureSsl(builder, connectionDetails);
122119
builder.withKeyspace(this.properties.getKeyspaceName());
123120
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
124121
return builder;
@@ -131,30 +128,11 @@ private void configureAuthentication(CqlSessionBuilder builder, CassandraConnect
131128
}
132129
}
133130

134-
private void configureSsl(CqlSessionBuilder builder, SslBundles sslBundles) {
135-
Ssl properties = this.properties.getSsl();
136-
if (properties == null || !properties.isEnabled()) {
131+
private void configureSsl(CqlSessionBuilder builder, CassandraConnectionDetails connectionDetails) {
132+
SslBundle sslBundle = connectionDetails.getSslBundle();
133+
if (sslBundle == null) {
137134
return;
138135
}
139-
String bundleName = properties.getBundle();
140-
if (!StringUtils.hasLength(bundleName)) {
141-
configureDefaultSslContext(builder);
142-
}
143-
else {
144-
configureSsl(builder, sslBundles.getBundle(bundleName));
145-
}
146-
}
147-
148-
private void configureDefaultSslContext(CqlSessionBuilder builder) {
149-
try {
150-
builder.withSslContext(SSLContext.getDefault());
151-
}
152-
catch (NoSuchAlgorithmException ex) {
153-
throw new IllegalStateException("Could not setup SSL default context for Cassandra", ex);
154-
}
155-
}
156-
157-
private void configureSsl(CqlSessionBuilder builder, SslBundle sslBundle) {
158136
SslOptions options = sslBundle.getOptions();
159137
Assert.state(options.getEnabledProtocols() == null, "SSL protocol options cannot be specified with Cassandra");
160138
builder
@@ -320,8 +298,11 @@ static final class PropertiesCassandraConnectionDetails implements CassandraConn
320298

321299
private final CassandraProperties properties;
322300

323-
private PropertiesCassandraConnectionDetails(CassandraProperties properties) {
301+
private final SslBundles sslBundles;
302+
303+
private PropertiesCassandraConnectionDetails(CassandraProperties properties, SslBundles sslBundles) {
324304
this.properties = properties;
305+
this.sslBundles = sslBundles;
325306
}
326307

327308
@Override
@@ -346,6 +327,19 @@ public String getLocalDatacenter() {
346327
return this.properties.getLocalDatacenter();
347328
}
348329

330+
@Override
331+
public SslBundle getSslBundle() {
332+
Ssl ssl = this.properties.getSsl();
333+
if (ssl == null || !ssl.isEnabled()) {
334+
return null;
335+
}
336+
if (StringUtils.hasLength(ssl.getBundle())) {
337+
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
338+
return this.sslBundles.getBundle(ssl.getBundle());
339+
}
340+
return SslBundle.systemDefault();
341+
}
342+
349343
private Node asNode(String contactPoint) {
350344
int i = contactPoint.lastIndexOf(':');
351345
if (i >= 0) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraConnectionDetails.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

2121
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
22+
import org.springframework.boot.ssl.SslBundle;
2223

2324
/**
2425
* Details required to establish a connection to a Cassandra service.
@@ -59,6 +60,15 @@ default String getPassword() {
5960
*/
6061
String getLocalDatacenter();
6162

63+
/**
64+
* SSL bundle to use.
65+
* @return the SSL bundle to use
66+
* @since 3.5.0
67+
*/
68+
default SslBundle getSslBundle() {
69+
return null;
70+
}
71+
6272
/**
6373
* A Cassandra node.
6474
*

spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.testcontainers.cassandra.CassandraContainer;
2323

2424
import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails;
25+
import org.springframework.boot.ssl.SslBundle;
2526
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
2627
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
2728
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@@ -75,6 +76,11 @@ public String getLocalDatacenter() {
7576
return getContainer().getLocalDatacenter();
7677
}
7778

79+
@Override
80+
public SslBundle getSslBundle() {
81+
return super.getSslBundle();
82+
}
83+
7884
}
7985

8086
}

spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/DeprecatedCassandraContainerConnectionDetailsFactory.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.testcontainers.containers.CassandraContainer;
2323

2424
import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails;
25+
import org.springframework.boot.ssl.SslBundle;
2526
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
2627
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
2728
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@@ -78,6 +79,11 @@ public String getLocalDatacenter() {
7879
return getContainer().getLocalDatacenter();
7980
}
8081

82+
@Override
83+
public SslBundle getSslBundle() {
84+
return super.getSslBundle();
85+
}
86+
8187
}
8288

8389
}

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-cassandra/src/dockerTest/java/smoketest/data/cassandra/SampleCassandraApplicationReactiveSslTests.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
2828
import org.springframework.beans.factory.annotation.Autowired;
2929
import org.springframework.boot.test.context.SpringBootTest;
3030
import org.springframework.boot.test.context.TestConfiguration;
31+
import org.springframework.boot.testcontainers.service.connection.JksKeyStore;
32+
import org.springframework.boot.testcontainers.service.connection.JksTrustStore;
3133
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
3234
import org.springframework.boot.testsupport.container.TestImage;
3335
import org.springframework.context.annotation.Bean;
@@ -43,15 +45,13 @@
4345
@Testcontainers(disabledWithoutDocker = true)
4446
@SpringBootTest(properties = { "spring.cassandra.schema-action=create-if-not-exists",
4547
"spring.cassandra.connection.connect-timeout=60s", "spring.cassandra.connection.init-query-timeout=60s",
46-
"spring.cassandra.request.timeout=60s", "spring.cassandra.ssl.bundle=client",
47-
"spring.ssl.bundle.jks.client.keystore.location=classpath:ssl/test-client.p12",
48-
"spring.ssl.bundle.jks.client.keystore.password=password",
49-
"spring.ssl.bundle.jks.client.truststore.location=classpath:ssl/test-ca.p12",
50-
"spring.ssl.bundle.jks.client.truststore.password=password" })
48+
"spring.cassandra.request.timeout=60s" })
5149
class SampleCassandraApplicationReactiveSslTests {
5250

5351
@Container
5452
@ServiceConnection
53+
@JksTrustStore(location = "classpath:ssl/test-ca.p12", password = "password")
54+
@JksKeyStore(location = "classpath:ssl/test-client.p12", password = "password")
5555
static final SecureCassandraContainer cassandra = TestImage.container(SecureCassandraContainer.class);
5656

5757
@Autowired

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-cassandra/src/dockerTest/java/smoketest/data/cassandra/SampleCassandraApplicationSslTests.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@
2727
import org.springframework.beans.factory.annotation.Autowired;
2828
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
2929
import org.springframework.boot.test.context.TestConfiguration;
30+
import org.springframework.boot.testcontainers.service.connection.JksKeyStore;
31+
import org.springframework.boot.testcontainers.service.connection.JksTrustStore;
3032
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
3133
import org.springframework.boot.testsupport.container.TestImage;
3234
import org.springframework.context.annotation.Bean;
@@ -43,15 +45,13 @@
4345
@Testcontainers(disabledWithoutDocker = true)
4446
@DataCassandraTest(properties = { "spring.cassandra.schema-action=create-if-not-exists",
4547
"spring.cassandra.connection.connect-timeout=60s", "spring.cassandra.connection.init-query-timeout=60s",
46-
"spring.cassandra.request.timeout=60s", "spring.cassandra.ssl.bundle=client",
47-
"spring.ssl.bundle.jks.client.keystore.location=classpath:ssl/test-client.p12",
48-
"spring.ssl.bundle.jks.client.keystore.password=password",
49-
"spring.ssl.bundle.jks.client.truststore.location=classpath:ssl/test-ca.p12",
50-
"spring.ssl.bundle.jks.client.truststore.password=password" })
48+
"spring.cassandra.request.timeout=60s" })
5149
class SampleCassandraApplicationSslTests {
5250

5351
@Container
5452
@ServiceConnection
53+
@JksTrustStore(location = "classpath:ssl/test-ca.p12", password = "password")
54+
@JksKeyStore(location = "classpath:ssl/test-client.p12", password = "password")
5555
static final SecureCassandraContainer cassandra = TestImage.container(SecureCassandraContainer.class);
5656

5757
@Autowired

0 commit comments

Comments
 (0)