Add SSL service connection support for Couchbase
See gh-41137
This commit is contained in:
parent
9c520d6af7
commit
0ccf1b81d8
@ -93,15 +93,16 @@ public class CouchbaseAutoConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(CouchbaseConnectionDetails.class)
|
@ConditionalOnMissingBean(CouchbaseConnectionDetails.class)
|
||||||
PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails() {
|
PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails(ObjectProvider<SslBundles> sslBundles) {
|
||||||
return new PropertiesCouchbaseConnectionDetails(this.properties);
|
return new PropertiesCouchbaseConnectionDetails(this.properties, sslBundles.getIfAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public ClusterEnvironment couchbaseClusterEnvironment(
|
public ClusterEnvironment couchbaseClusterEnvironment(
|
||||||
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers, ObjectProvider<SslBundles> sslBundles) {
|
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers,
|
||||||
Builder builder = initializeEnvironmentBuilder(sslBundles.getIfAvailable());
|
CouchbaseConnectionDetails connectionDetails) {
|
||||||
|
Builder builder = initializeEnvironmentBuilder(connectionDetails);
|
||||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@ -143,7 +144,7 @@ public class CouchbaseAutoConfiguration {
|
|||||||
return Cluster.connect(connectionDetails.getConnectionString(), options);
|
return Cluster.connect(connectionDetails.getConnectionString(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClusterEnvironment.Builder initializeEnvironmentBuilder(SslBundles sslBundles) {
|
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails) {
|
||||||
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
|
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
|
||||||
Timeouts timeouts = this.properties.getEnv().getTimeouts();
|
Timeouts timeouts = this.properties.getEnv().getTimeouts();
|
||||||
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
|
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
|
||||||
@ -159,31 +160,24 @@ public class CouchbaseAutoConfiguration {
|
|||||||
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
|
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
|
||||||
.numKvConnections(io.getMinEndpoints())
|
.numKvConnections(io.getMinEndpoints())
|
||||||
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
|
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
|
||||||
if (this.properties.getEnv().getSsl().getEnabled()) {
|
SslBundle sslBundle = connectionDetails.getSslBundle();
|
||||||
configureSsl(builder, sslBundles);
|
if (sslBundle != null) {
|
||||||
|
configureSsl(builder, sslBundle);
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureSsl(Builder builder, SslBundles sslBundles) {
|
private void configureSsl(Builder builder, SslBundle sslBundle) {
|
||||||
Ssl sslProperties = this.properties.getEnv().getSsl();
|
Assert.state(!sslBundle.getOptions().isSpecified(), "SSL Options cannot be specified with Couchbase");
|
||||||
SslBundle sslBundle = (StringUtils.hasText(sslProperties.getBundle()))
|
|
||||||
? sslBundles.getBundle(sslProperties.getBundle()) : null;
|
|
||||||
Assert.state(sslBundle == null || !sslBundle.getOptions().isSpecified(),
|
|
||||||
"SSL Options cannot be specified with Couchbase");
|
|
||||||
builder.securityConfig((config) -> {
|
builder.securityConfig((config) -> {
|
||||||
config.enableTls(true);
|
config.enableTls(true);
|
||||||
TrustManagerFactory trustManagerFactory = getTrustManagerFactory(sslBundle);
|
TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory();
|
||||||
if (trustManagerFactory != null) {
|
if (trustManagerFactory != null) {
|
||||||
config.trustManagerFactory(trustManagerFactory);
|
config.trustManagerFactory(trustManagerFactory);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrustManagerFactory getTrustManagerFactory(SslBundle sslBundle) {
|
|
||||||
return (sslBundle != null) ? sslBundle.getManagers().getTrustManagerFactory() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnClass(ObjectMapper.class)
|
@ConditionalOnClass(ObjectMapper.class)
|
||||||
static class JacksonConfiguration {
|
static class JacksonConfiguration {
|
||||||
@ -247,8 +241,11 @@ public class CouchbaseAutoConfiguration {
|
|||||||
|
|
||||||
private final CouchbaseProperties properties;
|
private final CouchbaseProperties properties;
|
||||||
|
|
||||||
PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties) {
|
private final SslBundles sslBundles;
|
||||||
|
|
||||||
|
PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties, SslBundles sslBundles) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
this.sslBundles = sslBundles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -266,6 +263,19 @@ public class CouchbaseAutoConfiguration {
|
|||||||
return this.properties.getPassword();
|
return this.properties.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SslBundle getSslBundle() {
|
||||||
|
Ssl ssl = this.properties.getEnv().getSsl();
|
||||||
|
if (!ssl.getEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (StringUtils.hasLength(ssl.getBundle())) {
|
||||||
|
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
|
||||||
|
return this.sslBundles.getBundle(ssl.getBundle());
|
||||||
|
}
|
||||||
|
return SslBundle.systemDefault();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,6 +17,7 @@
|
|||||||
package org.springframework.boot.autoconfigure.couchbase;
|
package org.springframework.boot.autoconfigure.couchbase;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
||||||
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Details required to establish a connection to a Couchbase service.
|
* Details required to establish a connection to a Couchbase service.
|
||||||
@ -46,4 +47,13 @@ public interface CouchbaseConnectionDetails extends ConnectionDetails {
|
|||||||
*/
|
*/
|
||||||
String getPassword();
|
String getPassword();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL bundle to use.
|
||||||
|
* @return the SSL bundle to use
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
default SslBundle getSslBundle() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -196,7 +196,7 @@ class CouchbaseAutoConfigurationTests {
|
|||||||
testClusterEnvironment((env) -> {
|
testClusterEnvironment((env) -> {
|
||||||
SecurityConfig securityConfig = env.securityConfig();
|
SecurityConfig securityConfig = env.securityConfig();
|
||||||
assertThat(securityConfig.tlsEnabled()).isTrue();
|
assertThat(securityConfig.tlsEnabled()).isTrue();
|
||||||
assertThat(securityConfig.trustManagerFactory()).isNull();
|
assertThat(securityConfig.trustManagerFactory()).isNotNull();
|
||||||
}, "spring.couchbase.env.ssl.enabled=true");
|
}, "spring.couchbase.env.ssl.enabled=true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,6 +19,7 @@ package org.springframework.boot.testcontainers.service.connection.couchbase;
|
|||||||
import org.testcontainers.couchbase.CouchbaseContainer;
|
import org.testcontainers.couchbase.CouchbaseContainer;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.couchbase.CouchbaseConnectionDetails;
|
import org.springframework.boot.autoconfigure.couchbase.CouchbaseConnectionDetails;
|
||||||
|
import org.springframework.boot.ssl.SslBundle;
|
||||||
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
|
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
|
||||||
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
|
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
|
||||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
@ -66,6 +67,11 @@ class CouchbaseContainerConnectionDetailsFactory
|
|||||||
return getContainer().getConnectionString();
|
return getContainer().getConnectionString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SslBundle getSslBundle() {
|
||||||
|
return super.getSslBundle();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -26,6 +26,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.PemKeyStore;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.PemTrustStore;
|
||||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
import org.springframework.boot.testsupport.container.TestImage;
|
import org.springframework.boot.testsupport.container.TestImage;
|
||||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||||
@ -38,16 +40,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
* @author Scott Frederick
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
@Testcontainers(disabledWithoutDocker = true)
|
@Testcontainers(disabledWithoutDocker = true)
|
||||||
@SpringBootTest(properties = { "spring.couchbase.env.ssl.bundle=client", "spring.data.couchbase.bucket-name=cbbucket",
|
@SpringBootTest(properties = { "spring.data.couchbase.bucket-name=cbbucket" })
|
||||||
"spring.ssl.bundle.pem.client.keystore.certificate=classpath:ssl/test-client.crt",
|
|
||||||
"spring.ssl.bundle.pem.client.keystore.private-key=classpath:ssl/test-client.key",
|
|
||||||
"spring.ssl.bundle.pem.client.truststore.certificate=classpath:ssl/test-ca.crt" })
|
|
||||||
class SampleCouchbaseApplicationReactiveSslTests {
|
class SampleCouchbaseApplicationReactiveSslTests {
|
||||||
|
|
||||||
private static final String BUCKET_NAME = "cbbucket";
|
private static final String BUCKET_NAME = "cbbucket";
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
@ServiceConnection
|
@ServiceConnection
|
||||||
|
@PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key")
|
||||||
|
@PemTrustStore(certificate = "classpath:ssl/test-ca.crt")
|
||||||
static final CouchbaseContainer couchbase = TestImage.container(SecureCouchbaseContainer.class)
|
static final CouchbaseContainer couchbase = TestImage.container(SecureCouchbaseContainer.class)
|
||||||
.withBucket(new BucketDefinition(BUCKET_NAME));
|
.withBucket(new BucketDefinition(BUCKET_NAME));
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -24,6 +24,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest;
|
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.PemKeyStore;
|
||||||
|
import org.springframework.boot.testcontainers.service.connection.PemTrustStore;
|
||||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||||
import org.springframework.boot.testsupport.container.TestImage;
|
import org.springframework.boot.testsupport.container.TestImage;
|
||||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||||
@ -36,17 +38,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
* @author Scott Frederick
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
@Testcontainers(disabledWithoutDocker = true)
|
@Testcontainers(disabledWithoutDocker = true)
|
||||||
@DataCouchbaseTest(properties = { "spring.couchbase.env.ssl.bundle=client", "spring.couchbase.env.timeouts.connect=2m",
|
@DataCouchbaseTest(
|
||||||
"spring.data.couchbase.bucket-name=cbbucket",
|
properties = { "spring.couchbase.env.timeouts.connect=2m", "spring.data.couchbase.bucket-name=cbbucket" })
|
||||||
"spring.ssl.bundle.pem.client.keystore.certificate=classpath:ssl/test-client.crt",
|
|
||||||
"spring.ssl.bundle.pem.client.keystore.private-key=classpath:ssl/test-client.key",
|
|
||||||
"spring.ssl.bundle.pem.client.truststore.certificate=classpath:ssl/test-ca.crt" })
|
|
||||||
class SampleCouchbaseApplicationSslTests {
|
class SampleCouchbaseApplicationSslTests {
|
||||||
|
|
||||||
private static final String BUCKET_NAME = "cbbucket";
|
private static final String BUCKET_NAME = "cbbucket";
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
@ServiceConnection
|
@ServiceConnection
|
||||||
|
@PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key")
|
||||||
|
@PemTrustStore(certificate = "classpath:ssl/test-ca.crt")
|
||||||
static final CouchbaseContainer couchbase = TestImage.container(SecureCouchbaseContainer.class)
|
static final CouchbaseContainer couchbase = TestImage.container(SecureCouchbaseContainer.class)
|
||||||
.withBucket(new BucketDefinition(BUCKET_NAME));
|
.withBucket(new BucketDefinition(BUCKET_NAME));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user