Skip to content

Commit 6824573

Browse files
scottfrederickphilwebb
authored andcommitted
Add SSL bundle support to Cassandra auto-configuration
Update Cassandra auto-configuration so that an SSL can be configured via an SSL bundle. Closes gh-25602
1 parent 909c09c commit 6824573

File tree

4 files changed

+156
-16
lines changed

4 files changed

+156
-16
lines changed

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

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
3333
import com.datastax.oss.driver.api.core.config.DriverOption;
3434
import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder;
35+
import com.datastax.oss.driver.api.core.ssl.ProgrammaticSslEngineFactory;
3536
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader;
3637
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultProgrammaticDriverConfigLoaderBuilder;
3738
import com.typesafe.config.Config;
@@ -43,16 +44,22 @@
4344
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.Connection;
4445
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.Controlconnection;
4546
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.Request;
47+
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.Ssl;
4648
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.Throttler;
4749
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties.ThrottlerType;
4850
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
4951
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5052
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5153
import org.springframework.boot.context.properties.PropertyMapper;
54+
import org.springframework.boot.ssl.SslBundle;
55+
import org.springframework.boot.ssl.SslBundles;
56+
import org.springframework.boot.ssl.SslOptions;
5257
import org.springframework.context.annotation.Bean;
5358
import org.springframework.context.annotation.Lazy;
5459
import org.springframework.context.annotation.Scope;
5560
import org.springframework.core.io.Resource;
61+
import org.springframework.util.CollectionUtils;
62+
import org.springframework.util.StringUtils;
5663

5764
/**
5865
* {@link EnableAutoConfiguration Auto-configuration} for Cassandra.
@@ -66,6 +73,7 @@
6673
* @author Moritz Halbritter
6774
* @author Andy Wilkinson
6875
* @author Phillip Webb
76+
* @author Scott Frederick
6977
* @since 1.3.0
7078
*/
7179
@AutoConfiguration
@@ -106,10 +114,10 @@ public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
106114
@Scope("prototype")
107115
public CqlSessionBuilder cassandraSessionBuilder(DriverConfigLoader driverConfigLoader,
108116
CassandraConnectionDetails connectionDetails,
109-
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
117+
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers, ObjectProvider<SslBundles> sslBundles) {
110118
CqlSessionBuilder builder = CqlSession.builder().withConfigLoader(driverConfigLoader);
111119
configureAuthentication(builder, connectionDetails);
112-
configureSsl(builder, connectionDetails);
120+
configureSsl(builder, connectionDetails, sslBundles.getIfAvailable());
113121
builder.withKeyspace(this.properties.getKeyspaceName());
114122
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
115123
return builder;
@@ -122,15 +130,38 @@ private void configureAuthentication(CqlSessionBuilder builder, CassandraConnect
122130
}
123131
}
124132

125-
private void configureSsl(CqlSessionBuilder builder, CassandraConnectionDetails connectionDetails) {
126-
if (connectionDetails instanceof PropertiesCassandraConnectionDetails && this.properties.isSsl()) {
127-
try {
128-
builder.withSslContext(SSLContext.getDefault());
129-
}
130-
catch (NoSuchAlgorithmException ex) {
131-
throw new IllegalStateException("Could not setup SSL default context for Cassandra", ex);
132-
}
133+
private void configureSsl(CqlSessionBuilder builder, CassandraConnectionDetails connectionDetails,
134+
SslBundles sslBundles) {
135+
if (!(connectionDetails instanceof PropertiesCassandraConnectionDetails)) {
136+
return;
137+
}
138+
Ssl properties = this.properties.getSsl();
139+
if (properties == null || !properties.isEnabled()) {
140+
return;
133141
}
142+
String bundleName = properties.getBundle();
143+
if (!StringUtils.hasLength(bundleName)) {
144+
configureDefaultSslContext(builder);
145+
}
146+
else {
147+
configureSsl(builder, sslBundles.getBundle(bundleName));
148+
}
149+
}
150+
151+
private void configureDefaultSslContext(CqlSessionBuilder builder) {
152+
try {
153+
builder.withSslContext(SSLContext.getDefault());
154+
}
155+
catch (NoSuchAlgorithmException ex) {
156+
throw new IllegalStateException("Could not setup SSL default context for Cassandra", ex);
157+
}
158+
}
159+
160+
private void configureSsl(CqlSessionBuilder builder, SslBundle sslBundle) {
161+
SslOptions options = sslBundle.getOptions();
162+
String[] ciphers = (!CollectionUtils.isEmpty(options.getCiphers()) ? null
163+
: options.getCiphers().toArray(String[]::new));
164+
builder.withSslEngineFactory(new ProgrammaticSslEngineFactory(sslBundle.createSslContext(), ciphers));
134165
}
135166

136167
@Bean(destroyMethod = "")

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -31,6 +31,7 @@
3131
* @author Phillip Webb
3232
* @author Mark Paluch
3333
* @author Stephane Nicoll
34+
* @author Scott Frederick
3435
* @since 1.3.0
3536
*/
3637
@ConfigurationProperties(prefix = "spring.cassandra")
@@ -89,9 +90,9 @@ public class CassandraProperties {
8990
private String schemaAction = "none";
9091

9192
/**
92-
* Enable SSL support.
93+
* SSL configuration.
9394
*/
94-
private boolean ssl = false;
95+
private Ssl ssl = new Ssl();
9596

9697
/**
9798
* Connection configuration.
@@ -185,11 +186,11 @@ public void setCompression(Compression compression) {
185186
this.compression = compression;
186187
}
187188

188-
public boolean isSsl() {
189+
public Ssl getSsl() {
189190
return this.ssl;
190191
}
191192

192-
public void setSsl(boolean ssl) {
193+
public void setSsl(Ssl ssl) {
193194
this.ssl = ssl;
194195
}
195196

@@ -217,6 +218,36 @@ public Controlconnection getControlconnection() {
217218
return this.controlconnection;
218219
}
219220

221+
public static class Ssl {
222+
223+
/**
224+
* Whether to enable SSL support.
225+
*/
226+
private Boolean enabled;
227+
228+
/**
229+
* SSL bundle name.
230+
*/
231+
private String bundle;
232+
233+
public boolean isEnabled() {
234+
return (this.enabled != null) ? this.enabled : this.bundle != null;
235+
}
236+
237+
public void setEnabled(boolean enabled) {
238+
this.enabled = enabled;
239+
}
240+
241+
public String getBundle() {
242+
return this.bundle;
243+
}
244+
245+
public void setBundle(String bundle) {
246+
this.bundle = bundle;
247+
}
248+
249+
}
250+
220251
public static class Connection {
221252

222253
/**

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfigurationTests.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@
3434

3535
import org.springframework.boot.autoconfigure.AutoConfigurations;
3636
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration.PropertiesCassandraConnectionDetails;
37+
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
38+
import org.springframework.boot.ssl.NoSuchSslBundleException;
3739
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3840
import org.springframework.context.annotation.Bean;
3941
import org.springframework.context.annotation.Configuration;
4042

4143
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.assertj.core.api.Assertions.assertThatException;
4245
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4346

4447
/**
@@ -50,11 +53,12 @@
5053
* @author Moritz Halbritter
5154
* @author Andy Wilkinson
5255
* @author Phillip Webb
56+
* @author Scott Frederick
5357
*/
5458
class CassandraAutoConfigurationTests {
5559

5660
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
57-
.withConfiguration(AutoConfigurations.of(CassandraAutoConfiguration.class));
61+
.withConfiguration(AutoConfigurations.of(CassandraAutoConfiguration.class, SslAutoConfiguration.class));
5862

5963
@Test
6064
void cqlSessionBuildHasScopePrototype() {
@@ -67,6 +71,53 @@ void cqlSessionBuildHasScopePrototype() {
6771
});
6872
}
6973

74+
@Test
75+
void cqlSessionBuilderWithNoSslConfiguration() {
76+
this.contextRunner.run((context) -> {
77+
CqlSessionBuilder builder = context.getBean(CqlSessionBuilder.class);
78+
assertThat(builder).hasFieldOrPropertyWithValue("programmaticSslFactory", false);
79+
});
80+
}
81+
82+
@Test
83+
void cqlSessionBuilderWithSslEnabled() {
84+
this.contextRunner.withPropertyValues("spring.cassandra.ssl.enabled=true").run((context) -> {
85+
CqlSessionBuilder builder = context.getBean(CqlSessionBuilder.class);
86+
assertThat(builder).hasFieldOrPropertyWithValue("programmaticSslFactory", true);
87+
});
88+
}
89+
90+
@Test
91+
void cqlSessionBuilderWithSslBundle() {
92+
this.contextRunner
93+
.withPropertyValues("spring.cassandra.ssl.bundle=test-bundle",
94+
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks",
95+
"spring.ssl.bundle.jks.test-bundle.keystore.password=secret",
96+
"spring.ssl.bundle.jks.test-bundle.key.password=password")
97+
.run((context) -> {
98+
CqlSessionBuilder builder = context.getBean(CqlSessionBuilder.class);
99+
assertThat(builder).hasFieldOrPropertyWithValue("programmaticSslFactory", true);
100+
});
101+
}
102+
103+
@Test
104+
void cqlSessionBuilderWithSslBundleAndSslDisabled() {
105+
this.contextRunner
106+
.withPropertyValues("spring.cassandra.ssl.enabled=false", "spring.cassandra.ssl.bundle=test-bundle")
107+
.run((context) -> {
108+
CqlSessionBuilder builder = context.getBean(CqlSessionBuilder.class);
109+
assertThat(builder).hasFieldOrPropertyWithValue("programmaticSslFactory", false);
110+
});
111+
}
112+
113+
@Test
114+
void cqlSessionBuilderWithInvalidSslBundle() {
115+
this.contextRunner.withPropertyValues("spring.cassandra.ssl.bundle=test-bundle")
116+
.run((context) -> assertThatException().isThrownBy(() -> context.getBean(CqlSessionBuilder.class))
117+
.withRootCauseInstanceOf(NoSuchSslBundleException.class)
118+
.withMessageContaining("test-bundle"));
119+
}
120+
70121
@Test
71122
void driverConfigLoaderWithDefaultConfiguration() {
72123
this.contextRunner.run((context) -> {

spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,33 @@ If the port is the same for all your contact points you can use a shortcut and o
374374
TIP: Those two examples are identical as the port default to `9042`.
375375
If you need to configure the port, use `spring.cassandra.port`.
376376

377+
The auto-configured `CqlSession` can be configured to use SSL for communication with the server by setting the properties as shown in this example:
378+
379+
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
380+
----
381+
spring:
382+
cassandra:
383+
keyspace-name: "mykeyspace"
384+
contact-points: "cassandrahost1,cassandrahost2"
385+
local-datacenter: "datacenter1"
386+
ssl:
387+
enabled: true
388+
----
389+
390+
Custom SSL trust material can be configured in an <<features#features.ssl,SSL bundle>> and applied to the `CqlSession` as shown in this example:
391+
392+
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
393+
----
394+
spring:
395+
cassandra:
396+
keyspace-name: "mykeyspace"
397+
contact-points: "cassandrahost1,cassandrahost2"
398+
local-datacenter: "datacenter1"
399+
ssl:
400+
bundle: "example"
401+
----
402+
403+
377404
[NOTE]
378405
====
379406
The Cassandra driver has its own configuration infrastructure that loads an `application.conf` at the root of the classpath.

0 commit comments

Comments
 (0)