diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java index 85a735a07c..f6b54204d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java @@ -93,15 +93,16 @@ public class CouchbaseAutoConfiguration { @Bean @ConditionalOnMissingBean(CouchbaseConnectionDetails.class) - PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails() { - return new PropertiesCouchbaseConnectionDetails(this.properties); + PropertiesCouchbaseConnectionDetails couchbaseConnectionDetails(ObjectProvider sslBundles) { + return new PropertiesCouchbaseConnectionDetails(this.properties, sslBundles.getIfAvailable()); } @Bean @ConditionalOnMissingBean public ClusterEnvironment couchbaseClusterEnvironment( - ObjectProvider customizers, ObjectProvider sslBundles) { - Builder builder = initializeEnvironmentBuilder(sslBundles.getIfAvailable()); + ObjectProvider customizers, + CouchbaseConnectionDetails connectionDetails) { + Builder builder = initializeEnvironmentBuilder(connectionDetails); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } @@ -143,7 +144,7 @@ public class CouchbaseAutoConfiguration { return Cluster.connect(connectionDetails.getConnectionString(), options); } - private ClusterEnvironment.Builder initializeEnvironmentBuilder(SslBundles sslBundles) { + private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseConnectionDetails connectionDetails) { ClusterEnvironment.Builder builder = ClusterEnvironment.builder(); Timeouts timeouts = this.properties.getEnv().getTimeouts(); builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue()) @@ -159,31 +160,24 @@ public class CouchbaseAutoConfiguration { builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints()) .numKvConnections(io.getMinEndpoints()) .idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout())); - if (this.properties.getEnv().getSsl().getEnabled()) { - configureSsl(builder, sslBundles); + SslBundle sslBundle = connectionDetails.getSslBundle(); + if (sslBundle != null) { + configureSsl(builder, sslBundle); } return builder; } - private void configureSsl(Builder builder, SslBundles sslBundles) { - Ssl sslProperties = this.properties.getEnv().getSsl(); - 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"); + private void configureSsl(Builder builder, SslBundle sslBundle) { + Assert.state(!sslBundle.getOptions().isSpecified(), "SSL Options cannot be specified with Couchbase"); builder.securityConfig((config) -> { config.enableTls(true); - TrustManagerFactory trustManagerFactory = getTrustManagerFactory(sslBundle); + TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory(); if (trustManagerFactory != null) { config.trustManagerFactory(trustManagerFactory); } }); } - private TrustManagerFactory getTrustManagerFactory(SslBundle sslBundle) { - return (sslBundle != null) ? sslBundle.getManagers().getTrustManagerFactory() : null; - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ObjectMapper.class) static class JacksonConfiguration { @@ -247,8 +241,11 @@ public class CouchbaseAutoConfiguration { private final CouchbaseProperties properties; - PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties) { + private final SslBundles sslBundles; + + PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties, SslBundles sslBundles) { this.properties = properties; + this.sslBundles = sslBundles; } @Override @@ -266,6 +263,19 @@ public class CouchbaseAutoConfiguration { 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(); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseConnectionDetails.java index f4ca1c76ee..8b5094b8e1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseConnectionDetails.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.couchbase; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.ssl.SslBundle; /** * Details required to establish a connection to a Couchbase service. @@ -46,4 +47,13 @@ public interface CouchbaseConnectionDetails extends ConnectionDetails { */ String getPassword(); + /** + * SSL bundle to use. + * @return the SSL bundle to use + * @since 3.5.0 + */ + default SslBundle getSslBundle() { + return null; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java index 73b5f3ded9..dda2e9a5be 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -196,7 +196,7 @@ class CouchbaseAutoConfigurationTests { testClusterEnvironment((env) -> { SecurityConfig securityConfig = env.securityConfig(); assertThat(securityConfig.tlsEnabled()).isTrue(); - assertThat(securityConfig.trustManagerFactory()).isNull(); + assertThat(securityConfig.trustManagerFactory()).isNotNull(); }, "spring.couchbase.env.ssl.enabled=true"); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java index 3d6ae0088b..cf67683815 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java @@ -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"); * 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.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.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; @@ -66,6 +67,11 @@ class CouchbaseContainerConnectionDetailsFactory return getContainer().getConnectionString(); } + @Override + public SslBundle getSslBundle() { + return super.getSslBundle(); + } + } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationReactiveSslTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationReactiveSslTests.java index 6f69730257..d1f9d62145 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationReactiveSslTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationReactiveSslTests.java @@ -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"); * 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.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.testsupport.container.TestImage; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; @@ -38,16 +40,15 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Scott Frederick */ @Testcontainers(disabledWithoutDocker = true) -@SpringBootTest(properties = { "spring.couchbase.env.ssl.bundle=client", "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" }) +@SpringBootTest(properties = { "spring.data.couchbase.bucket-name=cbbucket" }) class SampleCouchbaseApplicationReactiveSslTests { private static final String BUCKET_NAME = "cbbucket"; @Container @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) .withBucket(new BucketDefinition(BUCKET_NAME)); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationSslTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationSslTests.java index e0fbdd0ae9..543dd001e6 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationSslTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/dockerTest/java/smoketest/data/couchbase/SampleCouchbaseApplicationSslTests.java @@ -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"); * 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.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.testsupport.container.TestImage; import org.springframework.data.couchbase.core.CouchbaseTemplate; @@ -36,17 +38,16 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Scott Frederick */ @Testcontainers(disabledWithoutDocker = true) -@DataCouchbaseTest(properties = { "spring.couchbase.env.ssl.bundle=client", "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" }) +@DataCouchbaseTest( + properties = { "spring.couchbase.env.timeouts.connect=2m", "spring.data.couchbase.bucket-name=cbbucket" }) class SampleCouchbaseApplicationSslTests { private static final String BUCKET_NAME = "cbbucket"; @Container @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) .withBucket(new BucketDefinition(BUCKET_NAME));