Add SSL bundle support to Couchbase auto-configuration

Update Couchbase auto-configuration so that an SSL can be configured
via an SSL bundle.

Closes gh-34811
pull/35107/head
Scott Frederick 2 years ago committed by Phillip Webb
parent 682457377a
commit 1618aa2dac

@ -44,6 +44,8 @@ import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfigurati
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ -69,8 +71,7 @@ public class CouchbaseAutoConfiguration {
private final CouchbaseProperties properties;
CouchbaseAutoConfiguration(CouchbaseProperties properties,
ObjectProvider<CouchbaseConnectionDetails> connectionDetails) {
CouchbaseAutoConfiguration(CouchbaseProperties properties) {
this.properties = properties;
}
@ -83,8 +84,8 @@ public class CouchbaseAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseConnectionDetails connectionDetails,
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
Builder builder = initializeEnvironmentBuilder(connectionDetails);
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers, ObjectProvider<SslBundles> sslBundles) {
Builder builder = initializeEnvironmentBuilder(connectionDetails, sslBundles.getIfAvailable());
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@ -99,7 +100,8 @@ public class CouchbaseAutoConfiguration {
return Cluster.connect(connectionDetails.getConnectionString(), options);
}
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails) {
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails,
SslBundles sslBundles) {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
Timeouts timeouts = this.properties.getEnv().getTimeouts();
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
@ -117,13 +119,28 @@ public class CouchbaseAutoConfiguration {
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
if ((connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
&& this.properties.getEnv().getSsl().getEnabled()) {
builder.securityConfig((config) -> config.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl())));
configureSsl(builder, sslBundles);
}
return builder;
}
private TrustManagerFactory getTrustManagerFactory(CouchbaseProperties.Ssl ssl) {
private void configureSsl(Builder builder, SslBundles sslBundles) {
builder.securityConfig((config) -> config.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl(), sslBundles)));
}
private TrustManagerFactory getTrustManagerFactory(CouchbaseProperties.Ssl ssl, SslBundles sslBundles) {
if (ssl.getKeyStore() != null) {
return loadTrustManagerFactory(ssl);
}
if (ssl.getBundle() != null) {
SslBundle bundle = sslBundles.getBundle(ssl.getBundle());
return bundle.getManagers().getTrustManagerFactory();
}
throw new IllegalStateException("A key store or bundle must be configured when SSL is enabled");
}
private TrustManagerFactory loadTrustManagerFactory(CouchbaseProperties.Ssl ssl) {
String resource = ssl.getKeyStore();
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -148,8 +148,8 @@ public class CouchbaseProperties {
public static class Ssl {
/**
* Whether to enable SSL support. Enabled automatically if a "keyStore" is
* provided unless specified otherwise.
* Whether to enable SSL support. Enabled automatically if a "keyStore" or
* "bundle" is provided unless specified otherwise.
*/
private Boolean enabled;
@ -163,8 +163,14 @@ public class CouchbaseProperties {
*/
private String keyStorePassword;
/**
* SSL bundle name.
*/
private String bundle;
public Boolean getEnabled() {
return (this.enabled != null) ? this.enabled : StringUtils.hasText(this.keyStore);
return (this.enabled != null) ? this.enabled
: StringUtils.hasText(this.keyStore) || StringUtils.hasText(this.bundle);
}
public void setEnabled(Boolean enabled) {
@ -187,6 +193,14 @@ public class CouchbaseProperties {
this.keyStorePassword = keyStorePassword;
}
public String getBundle() {
return this.bundle;
}
public void setBundle(String bundle) {
this.bundle = bundle;
}
}
public static class Timeouts {

@ -36,6 +36,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.PropertiesCouchbaseConnectionDetails;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -56,7 +58,7 @@ import static org.mockito.Mockito.mock;
class CouchbaseAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class, SslAutoConfiguration.class));
@Test
void connectionStringIsRequired() {
@ -179,7 +181,7 @@ class CouchbaseAutoConfigurationTests {
}
@Test
void enableSslNoEnabledFlag() {
void enableSslWithKeyStore() {
testClusterEnvironment((env) -> {
SecurityConfig securityConfig = env.securityConfig();
assertThat(securityConfig.tlsEnabled()).isTrue();
@ -187,6 +189,30 @@ class CouchbaseAutoConfigurationTests {
}, "spring.couchbase.env.ssl.keyStore=classpath:test.jks", "spring.couchbase.env.ssl.keyStorePassword=secret");
}
@Test
void enableSslWithBundle() {
testClusterEnvironment((env) -> {
SecurityConfig securityConfig = env.securityConfig();
assertThat(securityConfig.tlsEnabled()).isTrue();
assertThat(securityConfig.trustManagerFactory()).isNotNull();
}, "spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.test-bundle.keystore.password=secret",
"spring.couchbase.env.ssl.bundle=test-bundle");
}
@Test
void enableSslWithInvalidBundle() {
this.contextRunner
.withPropertyValues("spring.couchbase.connection-string=localhost",
"spring.couchbase.env.ssl.bundle=test-bundle")
.run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).rootCause()
.isInstanceOf(NoSuchSslBundleException.class)
.hasMessageContaining("test-bundle");
});
}
@Test
void disableSslEvenWithKeyStore() {
testClusterEnvironment((env) -> {
@ -197,6 +223,15 @@ class CouchbaseAutoConfigurationTests {
"spring.couchbase.env.ssl.keyStorePassword=secret");
}
@Test
void disableSslEvenWithBundle() {
testClusterEnvironment((env) -> {
SecurityConfig securityConfig = env.securityConfig();
assertThat(securityConfig.tlsEnabled()).isFalse();
assertThat(securityConfig.trustManagerFactory()).isNull();
}, "spring.couchbase.env.ssl.enabled=false", "spring.couchbase.env.ssl.bundle=test-bundle");
}
private void testClusterEnvironment(Consumer<ClusterEnvironment> environmentConsumer, String... environment) {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.couchbase.connection-string=localhost")

@ -455,7 +455,7 @@ Generally, you provide the https://github.com/couchbaselabs/sdk-rfcs/blob/master
----
It is also possible to customize some of the `ClusterEnvironment` settings.
For instance, the following configuration changes the timeout to open a new `Bucket` and enables SSL support:
For instance, the following configuration changes the timeout to open a new `Bucket` and enables SSL support with a reference to a configured <<features#features.ssl,SSL bundle>>:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
@ -465,8 +465,7 @@ For instance, the following configuration changes the timeout to open a new `Buc
timeouts:
connect: "3s"
ssl:
key-store: "/location/of/keystore.jks"
key-store-password: "secret"
bundle: "example"
----
TIP: Check the `spring.couchbase.env.*` properties for more details.

Loading…
Cancel
Save