diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java index c1c6fbb6e8..353b2ef9c9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.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. @@ -92,21 +92,17 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { private JedisConnectionFactory createJedisConnectionFactory( ObjectProvider builderCustomizers) { JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers); - if (getSentinelConfig() != null) { - return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration); - } - if (getClusterConfiguration() != null) { - return new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration); - } - return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration); + return switch (this.mode) { + case STANDALONE -> new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration); + case CLUSTER -> new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration); + case SENTINEL -> new JedisConnectionFactory(getSentinelConfig(), clientConfiguration); + }; } private JedisClientConfiguration getJedisClientConfiguration( ObjectProvider builderCustomizers) { JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder()); - if (isSslEnabled()) { - applySsl(builder); - } + applySslIfNeeded(builder); RedisProperties.Pool pool = getProperties().getJedis().getPool(); if (isPoolEnabled(pool)) { applyPooling(pool, builder); @@ -126,18 +122,19 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { return builder; } - private void applySsl(JedisClientConfigurationBuilder builder) { - JedisSslClientConfigurationBuilder sslBuilder = builder.useSsl(); - if (getProperties().getSsl().getBundle() != null) { - SslBundle sslBundle = getSslBundles().getBundle(getProperties().getSsl().getBundle()); - sslBuilder.sslSocketFactory(sslBundle.createSslContext().getSocketFactory()); - SslOptions sslOptions = sslBundle.getOptions(); - SSLParameters sslParameters = new SSLParameters(); - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sslOptions.getCiphers()).to(sslParameters::setCipherSuites); - map.from(sslOptions.getEnabledProtocols()).to(sslParameters::setProtocols); - sslBuilder.sslParameters(sslParameters); + private void applySslIfNeeded(JedisClientConfigurationBuilder builder) { + SslBundle sslBundle = getSslBundle(); + if (sslBundle == null) { + return; } + JedisSslClientConfigurationBuilder sslBuilder = builder.useSsl(); + sslBuilder.sslSocketFactory(sslBundle.createSslContext().getSocketFactory()); + SslOptions sslOptions = sslBundle.getOptions(); + SSLParameters sslParameters = new SSLParameters(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sslOptions.getCiphers()).to(sslParameters::setCipherSuites); + map.from(sslOptions.getEnabledProtocols()).to(sslParameters::setProtocols); + sslBuilder.sslParameters(sslParameters); } private void applyPooling(RedisProperties.Pool pool, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java index d8f42daf20..734c747e51 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java @@ -116,19 +116,14 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { ObjectProvider clientConfigurationBuilderCustomizers, ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources) { - LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientConfigurationBuilderCustomizers, - clientOptionsBuilderCustomizers, clientResources, getProperties().getLettuce().getPool()); - return createLettuceConnectionFactory(clientConfig); - } - - private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) { - if (getSentinelConfig() != null) { - return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); - } - if (getClusterConfiguration() != null) { - return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); - } - return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); + LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration( + clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources, + getProperties().getLettuce().getPool()); + return switch (this.mode) { + case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); + case CLUSTER -> new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); + case SENTINEL -> new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); + }; } private LettuceClientConfiguration getLettuceClientConfiguration( @@ -136,11 +131,12 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { ObjectProvider clientOptionsBuilderCustomizers, ClientResources clientResources, Pool pool) { LettuceClientConfigurationBuilder builder = createBuilder(pool); - applyProperties(builder); + SslBundle sslBundle = getSslBundle(); + applyProperties(builder, sslBundle); if (StringUtils.hasText(getProperties().getUrl())) { customizeConfigurationFromUrl(builder); } - builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers)); + builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers, sslBundle)); builder.clientResources(clientResources); clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); @@ -153,8 +149,8 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { return LettuceClientConfiguration.builder(); } - private void applyProperties(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) { - if (isSslEnabled()) { + private void applyProperties(LettuceClientConfigurationBuilder builder, SslBundle sslBundle) { + if (sslBundle != null) { builder.useSsl(); } if (getProperties().getTimeout() != null) { @@ -195,14 +191,14 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { } private ClientOptions createClientOptions( - ObjectProvider clientConfigurationBuilderCustomizers) { + ObjectProvider clientConfigurationBuilderCustomizers, + SslBundle sslBundle) { ClientOptions.Builder builder = initializeClientOptionsBuilder(); Duration connectTimeout = getProperties().getConnectTimeout(); if (connectTimeout != null) { builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build()); } - if (isSslEnabled() && getProperties().getSsl().getBundle() != null) { - SslBundle sslBundle = getSslBundles().getBundle(getProperties().getSsl().getBundle()); + if (sslBundle != null) { io.lettuce.core.SslOptions.Builder sslOptionsBuilder = io.lettuce.core.SslOptions.builder(); sslOptionsBuilder.keyManager(sslBundle.getManagers().getKeyManagerFactory()); sslOptionsBuilder.trustManager(sslBundle.getManagers().getTrustManagerFactory()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java index f7a836f23d..7b42223c69 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetails.java @@ -18,6 +18,11 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.List; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + /** * Adapts {@link RedisProperties} to {@link RedisConnectionDetails}. * @@ -32,8 +37,11 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails { private final RedisProperties properties; - PropertiesRedisConnectionDetails(RedisProperties properties) { + private final SslBundles sslBundles; + + PropertiesRedisConnectionDetails(RedisProperties properties, SslBundles sslBundles) { this.properties = properties; + this.sslBundles = sslBundles; } @Override @@ -52,8 +60,21 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails { public Standalone getStandalone() { RedisUrl redisUrl = getRedisUrl(); return (redisUrl != null) - ? Standalone.of(redisUrl.uri().getHost(), redisUrl.uri().getPort(), redisUrl.database()) - : Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase()); + ? Standalone.of(redisUrl.uri().getHost(), redisUrl.uri().getPort(), redisUrl.database(), getSslBundle()) + : Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase(), + getSslBundle()); + } + + private SslBundle getSslBundle() { + if (!this.properties.getSsl().isEnabled()) { + return null; + } + String bundleName = this.properties.getSsl().getBundle(); + if (StringUtils.hasLength(bundleName)) { + Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context"); + return this.sslBundles.getBundle(bundleName); + } + return SslBundle.systemDefault(); } @Override @@ -65,8 +86,7 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails { @Override public Cluster getCluster() { RedisProperties.Cluster cluster = this.properties.getCluster(); - List nodes = (cluster != null) ? asNodes(cluster.getNodes()) : null; - return (nodes != null) ? () -> nodes : null; + return (cluster != null) ? new PropertiesCluster(cluster) : null; } private RedisUrl getRedisUrl() { @@ -84,6 +104,29 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails { return new Node(host, port); } + /** + * {@link Cluster} implementation backed by properties. + */ + private class PropertiesCluster implements Cluster { + + private final List nodes; + + PropertiesCluster(RedisProperties.Cluster properties) { + this.nodes = asNodes(properties.getNodes()); + } + + @Override + public List getNodes() { + return this.nodes; + } + + @Override + public SslBundle getSslBundle() { + return PropertiesRedisConnectionDetails.this.getSslBundle(); + } + + } + /** * {@link Sentinel} implementation backed by properties. */ @@ -123,6 +166,11 @@ class PropertiesRedisConnectionDetails implements RedisConnectionDetails { return this.properties.getPassword(); } + @Override + public SslBundle getSslBundle() { + return PropertiesRedisConnectionDetails.this.getSslBundle(); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index fdef9695ff..c5480cc9a9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.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. @@ -16,12 +16,14 @@ package org.springframework.boot.autoconfigure.data.redis; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -51,8 +53,9 @@ public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(RedisConnectionDetails.class) - PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) { - return new PropertiesRedisConnectionDetails(properties); + PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties, + ObjectProvider sslBundles) { + return new PropertiesRedisConnectionDetails(properties, sslBundles.getIfAvailable()); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java index 4163ebc8eb..d3a703608e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java @@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails. import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Node; import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Sentinel; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; +import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisNode; @@ -62,6 +63,8 @@ abstract class RedisConnectionConfiguration { private final SslBundles sslBundles; + protected final Mode mode; + protected RedisConnectionConfiguration(RedisProperties properties, RedisConnectionDetails connectionDetails, ObjectProvider standaloneConfigurationProvider, ObjectProvider sentinelConfigurationProvider, @@ -73,6 +76,7 @@ abstract class RedisConnectionConfiguration { this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable(); this.connectionDetails = connectionDetails; this.sslBundles = sslBundles.getIfAvailable(); + this.mode = determineMode(); } protected final RedisStandaloneConfiguration getStandaloneConfig() { @@ -153,6 +157,17 @@ abstract class RedisConnectionConfiguration { return this.sslBundles; } + protected SslBundle getSslBundle() { + return switch (this.mode) { + case STANDALONE -> (this.connectionDetails.getStandalone() != null) + ? this.connectionDetails.getStandalone().getSslBundle() : null; + case CLUSTER -> (this.connectionDetails.getCluster() != null) + ? this.connectionDetails.getCluster().getSslBundle() : null; + case SENTINEL -> (this.connectionDetails.getSentinel() != null) + ? this.connectionDetails.getSentinel().getSslBundle() : null; + }; + } + protected final boolean isSslEnabled() { return getProperties().getSsl().isEnabled(); } @@ -178,4 +193,20 @@ abstract class RedisConnectionConfiguration { return this.connectionDetails; } + private Mode determineMode() { + if (getSentinelConfig() != null) { + return Mode.SENTINEL; + } + if (getClusterConfiguration() != null) { + return Mode.CLUSTER; + } + return Mode.STANDALONE; + } + + enum Mode { + + STANDALONE, CLUSTER, SENTINEL + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java index 047f6b2379..013aaee5d0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionDetails.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.List; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.ssl.SslBundle; import org.springframework.util.Assert; /** @@ -98,11 +99,58 @@ public interface RedisConnectionDetails extends ConnectionDetails { return 0; } - static Standalone of(String host, int port) { - return of(host, port, 0); + /** + * SSL bundle to use. + * @return the SSL bundle to use + * @since 3.5.0 + */ + default SslBundle getSslBundle() { + return null; } + /** + * Creates a new instance with the given host and port. + * @param host the host + * @param port the port + * @return the new instance + */ + static Standalone of(String host, int port) { + return of(host, port, 0, null); + } + + /** + * Creates a new instance with the given host, port and SSL bundle. + * @param host the host + * @param port the port + * @param sslBundle the SSL bundle + * @return the new instance + * @since 3.5.0 + */ + static Standalone of(String host, int port, SslBundle sslBundle) { + return of(host, port, 0, sslBundle); + } + + /** + * Creates a new instance with the given host, port and database. + * @param host the host + * @param port the port + * @param database the database + * @return the new instance + */ static Standalone of(String host, int port, int database) { + return of(host, port, database, null); + } + + /** + * Creates a new instance with the given host, port, database and SSL bundle. + * @param host the host + * @param port the port + * @param database the database + * @param sslBundle the SSL bundle + * @return the new instance + * @since 3.5.0 + */ + static Standalone of(String host, int port, int database, SslBundle sslBundle) { Assert.hasLength(host, "'host' must not be empty"); return new Standalone() { @@ -121,6 +169,10 @@ public interface RedisConnectionDetails extends ConnectionDetails { return database; } + @Override + public SslBundle getSslBundle() { + return sslBundle; + } }; } @@ -161,6 +213,15 @@ public interface RedisConnectionDetails extends ConnectionDetails { */ String getPassword(); + /** + * SSL bundle to use. + * @return the SSL bundle to use + * @since 3.5.0 + */ + default SslBundle getSslBundle() { + return null; + } + } /** @@ -175,6 +236,15 @@ public interface RedisConnectionDetails extends ConnectionDetails { */ List getNodes(); + /** + * 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/data/redis/PropertiesRedisConnectionDetailsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetailsTests.java index 29b14bc0bb..6b04454403 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetailsTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/PropertiesRedisConnectionDetailsTests.java @@ -18,40 +18,54 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Node; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundle; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link PropertiesRedisConnectionDetails}. * * @author Scott Frederick + * @author Moritz Halbritter */ class PropertiesRedisConnectionDetailsTests { - private final RedisProperties properties = new RedisProperties(); + private RedisProperties properties; + + private PropertiesRedisConnectionDetails connectionDetails; + + private DefaultSslBundleRegistry sslBundleRegistry; + + @BeforeEach + void setUp() { + this.properties = new RedisProperties(); + this.sslBundleRegistry = new DefaultSslBundleRegistry(); + this.connectionDetails = new PropertiesRedisConnectionDetails(this.properties, this.sslBundleRegistry); + } @Test void connectionIsConfiguredWithDefaults() { - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - RedisConnectionDetails.Standalone standalone = connectionDetails.getStandalone(); + RedisConnectionDetails.Standalone standalone = this.connectionDetails.getStandalone(); assertThat(standalone.getHost()).isEqualTo("localhost"); assertThat(standalone.getPort()).isEqualTo(6379); assertThat(standalone.getDatabase()).isEqualTo(0); - assertThat(connectionDetails.getSentinel()).isNull(); - assertThat(connectionDetails.getCluster()).isNull(); - assertThat(connectionDetails.getUsername()).isNull(); - assertThat(connectionDetails.getPassword()).isNull(); + assertThat(this.connectionDetails.getSentinel()).isNull(); + assertThat(this.connectionDetails.getCluster()).isNull(); + assertThat(this.connectionDetails.getUsername()).isNull(); + assertThat(this.connectionDetails.getPassword()).isNull(); } @Test void credentialsAreConfiguredFromUrlWithUsernameAndPassword() { this.properties.setUrl("redis://user:secret@example.com"); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getUsername()).isEqualTo("user"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(this.connectionDetails.getUsername()).isEqualTo("user"); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); } @Test @@ -59,9 +73,8 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setUrl("redis://user:@example.com"); this.properties.setUsername("notused"); this.properties.setPassword("notused"); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getUsername()).isEqualTo("user"); - assertThat(connectionDetails.getPassword()).isEmpty(); + assertThat(this.connectionDetails.getUsername()).isEqualTo("user"); + assertThat(this.connectionDetails.getPassword()).isEmpty(); } @Test @@ -69,9 +82,8 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setUrl("redis://:secret@example.com"); this.properties.setUsername("notused"); this.properties.setPassword("notused"); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getUsername()).isEmpty(); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(this.connectionDetails.getUsername()).isEmpty(); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); } @Test @@ -79,18 +91,16 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setUrl("redis://secret@example.com"); this.properties.setUsername("notused"); this.properties.setPassword("notused"); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getUsername()).isNull(); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(this.connectionDetails.getUsername()).isNull(); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); } @Test void credentialsAreConfiguredFromProperties() { this.properties.setUsername("user"); this.properties.setPassword("secret"); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getUsername()).isEqualTo("user"); - assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + assertThat(this.connectionDetails.getUsername()).isEqualTo("user"); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); } @Test @@ -99,8 +109,7 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setHost("notused"); this.properties.setPort(9999); this.properties.setDatabase(5); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - RedisConnectionDetails.Standalone standalone = connectionDetails.getStandalone(); + RedisConnectionDetails.Standalone standalone = this.connectionDetails.getStandalone(); assertThat(standalone.getHost()).isEqualTo("example.com"); assertThat(standalone.getPort()).isEqualTo(1234); assertThat(standalone.getDatabase()).isEqualTo(9999); @@ -110,7 +119,8 @@ class PropertiesRedisConnectionDetailsTests { void standaloneIsConfiguredFromUrlWithoutDatabase() { this.properties.setUrl("redis://example.com:1234"); this.properties.setDatabase(5); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); + PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties, + null); RedisConnectionDetails.Standalone standalone = connectionDetails.getStandalone(); assertThat(standalone.getHost()).isEqualTo("example.com"); assertThat(standalone.getPort()).isEqualTo(1234); @@ -122,8 +132,7 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setHost("example.com"); this.properties.setPort(1234); this.properties.setDatabase(5); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - RedisConnectionDetails.Standalone standalone = connectionDetails.getStandalone(); + RedisConnectionDetails.Standalone standalone = this.connectionDetails.getStandalone(); assertThat(standalone.getHost()).isEqualTo("example.com"); assertThat(standalone.getPort()).isEqualTo(1234); assertThat(standalone.getDatabase()).isEqualTo(5); @@ -134,8 +143,7 @@ class PropertiesRedisConnectionDetailsTests { RedisProperties.Cluster cluster = new RedisProperties.Cluster(); cluster.setNodes(List.of("localhost:1111", "127.0.0.1:2222", "[::1]:3333")); this.properties.setCluster(cluster); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); - assertThat(connectionDetails.getCluster().getNodes()).containsExactly(new Node("localhost", 1111), + assertThat(this.connectionDetails.getCluster().getNodes()).containsExactly(new Node("localhost", 1111), new Node("127.0.0.1", 2222), new Node("[::1]", 3333)); } @@ -145,7 +153,8 @@ class PropertiesRedisConnectionDetailsTests { sentinel.setNodes(List.of("localhost:1111", "127.0.0.1:2222", "[::1]:3333")); this.properties.setSentinel(sentinel); this.properties.setDatabase(5); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); + PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties, + null); assertThat(connectionDetails.getSentinel().getNodes()).containsExactly(new Node("localhost", 1111), new Node("127.0.0.1", 2222), new Node("[::1]", 3333)); assertThat(connectionDetails.getSentinel().getDatabase()).isEqualTo(5); @@ -158,8 +167,32 @@ class PropertiesRedisConnectionDetailsTests { this.properties.setSentinel(sentinel); this.properties.setUrl("redis://example.com:1234/9999"); this.properties.setDatabase(5); - PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties); + PropertiesRedisConnectionDetails connectionDetails = new PropertiesRedisConnectionDetails(this.properties, + null); assertThat(connectionDetails.getSentinel().getDatabase()).isEqualTo(9999); } + @Test + void shouldReturnSslBundle() { + SslBundle bundle1 = mock(SslBundle.class); + this.sslBundleRegistry.registerBundle("bundle-1", bundle1); + this.properties.getSsl().setBundle("bundle-1"); + SslBundle sslBundle = this.connectionDetails.getStandalone().getSslBundle(); + assertThat(sslBundle).isSameAs(bundle1); + } + + @Test + void shouldReturnSystemBundleIfSslIsEnabledButBundleNotSet() { + this.properties.getSsl().setEnabled(true); + SslBundle sslBundle = this.connectionDetails.getStandalone().getSslBundle(); + assertThat(sslBundle).isNotNull(); + } + + @Test + void shouldReturnNullIfSslIsNotEnabled() { + this.properties.getSsl().setEnabled(false); + SslBundle sslBundle = this.connectionDetails.getStandalone().getSslBundle(); + assertThat(sslBundle).isNull(); + } + } diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java index 8b6b3d46f5..1dec0840d2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactoryIntegrationTests.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. @@ -16,9 +16,12 @@ package org.springframework.boot.docker.compose.service.connection.redis; +import javax.net.ssl.SSLContext; + import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails.Standalone; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; @@ -39,6 +42,17 @@ class RedisDockerComposeConnectionDetailsFactoryIntegrationTests { assertConnectionDetails(connectionDetails); } + @DockerComposeTest(composeFile = "redis-ssl-compose.yaml", image = TestImage.REDIS, + additionalResources = { "ca.crt", "server.crt", "server.key", "client.crt", "client.key" }) + void runWithSslCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + Standalone standalone = connectionDetails.getStandalone(); + SslBundle sslBundle = standalone.getSslBundle(); + assertThat(sslBundle).isNotNull(); + SSLContext sslContext = sslBundle.createSslContext(); + assertThat(sslContext).isNotNull(); + } + @DockerComposeTest(composeFile = "redis-bitnami-compose.yaml", image = TestImage.BITNAMI_REDIS) void runWithBitnamiImageCreatesConnectionDetails(RedisConnectionDetails connectionDetails) { assertConnectionDetails(connectionDetails); diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/ca.crt b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/ca.crt new file mode 100644 index 0000000000..beed250b13 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/ca.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhjCCA26gAwIBAgIUfIkk29IT9OpbgfjL8oRIPSLjUcAwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNh +dGUgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAusN2 +KzQQUUxZSiI3ZZuZohFwq2KXSUNPdJ6rgD3/YKNTDSZXKZPO53kYPP0DXf0sm3CH +cyWSWVabyimZYuPWena1MElSL4ZpJ9WwkZoOQ3bPFK1utz6kMOwrgAUcky8H/rIK +j2JEBhkSHUIGr57NjUEwG1ygaSerM8RzWw1PtMq+C8LOu3v94qzE3NDg1QRpyvV9 +OmsLsjISd0ZmAJNi9vmiEH923KnPyiqnQmWKpYicdgQmX1GXylS22jZqAwaOkYGj +X8UdeyvrohkZkM0hn9uaSufQGEW4yKACn3PkjJtzi8drBIyjIi9YcAzBxZB9oVKq +XZMlltgO2fDMmIJi0Ngt0Ci7fCoEMqSocKyDKML6YLr9UWtx4bfsrk+rVO9Q/D/v +8RKgstv7dCf2KWRX3ZJEC0IBHS5gLNq0qqqVcGx3LcSyhdiKJOtSwAnNkHMh+jSQ +xLSlBjcSqTPiGTRK/Rddl+xnU/mBgk7ZBGNrUFaD5McMFjddS7Ih82aHnpQ1gekW +nUGv+Tm/G68h2BvZ5U2q+RfeOCgRW9i/AYW2jgT7IFnfjyUXgBQveauMAchomqFE +VLe95ZgViF6vmH34EKo3w9L5TQiwk/r53YlM7TSOTyDqx66t4zGYDsVMicpKmzi4 +2Rp8EpErARRyREUIKSvWs9O9+uT3+7arNLgHe5ECAwEAAaOBgTB/MB0GA1UdDgQW +BBRVMLDVqPECWaH6GruL9E52VcTrPjAfBgNVHSMEGDAWgBRVMLDVqPECWaH6GruL +9E52VcTrPjAPBgNVHRMBAf8EBTADAQH/MCwGA1UdEQQlMCOCC2V4YW1wbGUuY29t +gglsb2NhbGhvc3SCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsFAAOCAgEAeSpjCL3j +2GIFBNKr/5amLOYa0kZ6r1dJs+K6xvMsUvsBJ/QQsV5nYDMIoV/NYUd8SyYV4lEj +7LHX5ZbmJrvPk30LGEBG/5Vy2MIATrQrQ14S4nXtEdSnBvTQwPOOaHc+2dTp3YpM +f4ffELKWyispTifx1eqdiUJhURKeQBh+3W7zpyaiN4vJaqEDKGgFQtHA/OyZL2hZ +BpxHB0zpb2iDHV8MeyfOT7HQWUk6p13vdYm6EnyJT8fzWvE+TqYNbqFmB+CLRSXy +R3p1yaeTd4LnVknJ0UBKqEyul3ziHZDhKhBpwdglYOQz4eWjSFhikX9XZ8NaI38Q +QqLZVn0DsH2ztkjrQrUVgK2xn4aUuqoLDk4Hu6h5baUn+f2GLuzx+EXc/i3ikYvw +Y3JyufOgw6nGGFG+/QXEj85XtLPhN7Wm42z2e/BGzi0MLl65sfpEDXvFTA72Yzws +OYaeg/HxeYwUHQgs2fKl/LgV4chntSCvTqfNl6OnQafD/ISJNpx3xWR3HwF+ypFG +UaLE+e1soqEJbzL31U/6pypHLsj8Y8r9hJbZXo2ibnhjFV6fypUAP0rbIzaoWcrJ +T0Sbliz+KQTMzCcubiAi4bI/kZ5FJ4kkaHqUpIWzlx1h2WVJ65ASFDjBWb8eVmB6 +Dyno/RVFR/rUL5091gjGRXhLsi1oUHKdEzU= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.crt b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.crt new file mode 100644 index 0000000000..811d880fcb --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVx0wDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGb7tu0odSuOjeY1lHlh +sRR4PayAvlryjfrrp49hjoVTiL3d/Jo6Po5HlqwJcYuclm0EWQR5Vur/zYJpfUE7 +b8+E9Qwe50+YzfQ2tVFEdq/VfqemrYRGee+pMelOCI90enOKCxfpo6EHbz+WnUP0 +mnD8OAF9QpolSdWAMOGJoPdWX65KQvyMXvQbj9VIHmsx7NCaIOYxjHXB/dI2FmXV ++m4VT6mb8he9dXmgK/ozMq6XIPOAXe0n3dlfMTSEddeNeVwnBpr/n5e0cpwGFhdf +NNu5CI4ecipBhXljJi/4/47M/6hd69HwE05C4zyH4ZDZ2JTfaSKOLV+jYdBUqJP5 +dwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgeAMB0GA1Ud +DgQWBBRWiWOo9cm2IF/ZlhWLVjifLzYa/DAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAA5Wphtu2nBhY+QNOBOwXq4zF +N5qt2IYTLfR7xqpKhhXx9VkIjdPWpcsGuCuMmfPVNvQWE6iK0/jMMqToTj4H6K7e +MN74j0GwwcknT1P42tUzEpg8LKR8VMdhWhyqdniCDNWWuaz1iVSoF0S2i4jFSzH5 +1q3KMKMZ4niK5aJI0fAGa4fCjyuun1Mfg/qGBGwLnqDkIXjeAopZf4Jb64TtzjAs +j9NT6mYbe3E0tw3fHT9ihYdbZDZgSjeCsuq9OiRMVb0DWWmRoLmmOrlN8IJlHV/3 +WyI/ta4Cw5EZ0oaOg0lIyOxXyvElth1xIvh+kdqZSBsU0gNBri6ZIzYbbTh2KTTO +BJHQt9L5naWG27pDrIxBicWXS/MIYonktm3YgCLfuW3kWcVk8bIlNhfcoAYBBgfM +IEYSYEq+bH2IQ+YoWQz3AxjJ8gEuuSUP6R6mYY65FfpjkKgcpGBvw4EIAmqKDtPS +hlLY/F0XVj9KZzrMyH4/vonu+DAb/P7Zmt2fyk/dQO6bAc3ltRmJbJm4VJ2v/T8I +LVu2FtcUYgtLNtkWUPfdb3GSUUgkKlUpWSty31TKSUszJjW1oRykQhEko6o5U3S8 +ptQzXdApsb1lGOqewkubE25tIu2RLiNkKcjFOjJ/lu0vP9k76wWwRVnFLFvfo4lW +pgywiOifs5JbcCt0ZQ0= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.key b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.key new file mode 100644 index 0000000000..2ae0f49bf4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8Zvu27Sh1K46N +5jWUeWGxFHg9rIC+WvKN+uunj2GOhVOIvd38mjo+jkeWrAlxi5yWbQRZBHlW6v/N +gml9QTtvz4T1DB7nT5jN9Da1UUR2r9V+p6athEZ576kx6U4Ij3R6c4oLF+mjoQdv +P5adQ/SacPw4AX1CmiVJ1YAw4Ymg91ZfrkpC/Ixe9BuP1UgeazHs0Jog5jGMdcH9 +0jYWZdX6bhVPqZvyF711eaAr+jMyrpcg84Bd7Sfd2V8xNIR11415XCcGmv+fl7Ry +nAYWF18027kIjh5yKkGFeWMmL/j/jsz/qF3r0fATTkLjPIfhkNnYlN9pIo4tX6Nh +0FSok/l3AgMBAAECggEABXnBe3MwXAMQENzNypOiXK4VE3XMYkePfdsSK163byOD +w3ZeTgQNfU4g8LJK8/homzO0SQIJAdz2+ZFbpsp4A2W2zJ+1jvN5RuX/8/UcVhmk +tb1IL/LWCvx5/aoYBWkgIA70UfQJa2jDbdM0v5j/Gu9yE7GI14jh6DFC3xGMGV3b +fOwManxf7sDibCI1nGjnFYNGxninRr+tpb+a1KNbVzhett68LrgPmtph6B3HCPAJ +zBigk1Phgb8WHozTXxnLyw9/RdKJ0Ro4PFmtQv0EvCSlytptnF+0nXkqr3f851XS +bUWwYFchIFWPMhPfD5B3niNWCV42/sU/bQlk+BMQAQKBgQD6NvMq8EdYy2Y7fXT5 +FgB4s+7EkLgI2d5LUaCXCFgc6iZtCTQKUXj1rIWeRfGrFVCCe8qV+XIMKt/G5eEi +tn5ifHhktA2A8GK1scj026qHP3bVn0hMaUnkCF1UpDRKPiEO5G/apPtav8PbCNaX +GAimLGw+WZNZuv7+T33bEBeUdwKBgQDAwiidayLXkRkz2deefdDKcXQsB7RHFGGy +vfZPBCGqizxml+6ojJkkDsVUKL1IXFfyK9KpQAI6tezn4oktgu4jAQqkYY7QZobs +RpQx1dR+KxEm7ISDBTq/B1Q9cFKUKVvQQy8N2pnIbCdzb6MTOKLmJqFGTjr+5T8q +F32B5vkDAQKBgDCKfH42AwFc5EZiPlEcTZcdARMtKCa/bXqbKVZjjgR+AFpi0K+3 +womWoI1l8E5KYkYOEe0qaU+m+aaybgy37qjYkNqoe34qJFwvU1b9ToXScBFdRz9b +pbQRU1naSTKl/u/OrUxzeTfPwAU8H7VMOlFSiOVHp2he+J0JetcGtixdAoGBAIJQ +QMj7rxhxHcqyEVUy1b6nKNTDeJs9Kjd+uU/+CQyVCQaK3GvScY2w9rLIv/51f3dX +LRoDDf7HExxJSFgeVgQQJjOvSK+XQMvngzSVzQxm7TeVWpiBJpAS0l6e2xUTSODp +KpyBFsoqZBlkdaj+9xIFN66iILxGG4fHTbBOiDYBAoGBAOZMKjM5N/hGcCmik/6t +p/zBA2pN9O6zwPndITTsdyVWSlVqCZhXlRX47CerAN+/WVCidlh7Vp5Tuy75Wa77 +v16IDLO01txgWNobcLaM4VgFsyLi5JuxK73S18Vb1cKWdHFRF0LH3cUIq20fjpv6 +Odl4vjNOncXMZCLPHQ+bKWaf +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-ssl-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-ssl-compose.yaml new file mode 100644 index 0000000000..7f91f4077b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/redis-ssl-compose.yaml @@ -0,0 +1,21 @@ +services: + redis: + image: '{imageName}' + ports: + - '6379' + secrets: + - ssl-ca + - ssl-key + - ssl-cert + command: 'redis-server --tls-port 6379 --port 0 --tls-cert-file /run/secrets/ssl-cert --tls-key-file /run/secrets/ssl-key --tls-ca-cert-file /run/secrets/ssl-ca' + labels: + - 'org.springframework.boot.sslbundle.pem.keystore.certificate=client.crt' + - 'org.springframework.boot.sslbundle.pem.keystore.private-key=client.key' + - 'org.springframework.boot.sslbundle.pem.truststore.certificate=ca.crt' +secrets: + ssl-ca: + file: 'ca.crt' + ssl-key: + file: 'server.key' + ssl-cert: + file: 'server.crt' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.crt b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.crt new file mode 100644 index 0000000000..57c66cc78a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVxwwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsllxsSQzTTJlNHMfXC2b +CIXCPsfCgCBl7FbPz828jwJk+EYcXh0+WTFGks0WxSwb8NQza5UtyCUDEueZj9fV +j5mWBY97WCu01Sl/3xClHmYisXfyyv27GKec7PaSOurCm2JDkyHRNumiJROa4jte +N0GOHzw7FYsM3779TuNw14/gtW+eBrGnvgrpU7fbUvx42Di6ftGYQUwIi+3uIaqT +//i7ktDMaAQJtkL6haTzZ5JN2qKO5a34/WRz/ApvPw3lpDV8c4qoTk3C0Bg9MP+a +DnZtjtLBSN9CJWwr+n11QaMgHTotEKsOahGdi3J2zYxCvJP0LT+hjN2O9aRzSMIs +MwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgZAMB0GA1Ud +DgQWBBS9XQHGwJZhG0olAGM1UMNuwZ65DzAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAhBcqm5UQahn8iFMETXvfLMR6 +OOPijsHQ5lVfhig08s46a9O5eaJ9EYSYyiDnxYvZ4gYVH03f/kPwNLamvGR5KIBQ +R0DltkPPX4a11/vjwlSq1cXAt9r59nY+sNcVXWgIWH7zNodL8lyTpYhqvB2wEQkx +t2/JKZ8A0sGjed4S6I5HofYd7bnBxQZgfZShQ2SdDbzbcyg4SCEb8ghwnsH0KNZo +jJF+20RpK2VMViE6lylLTEMd/PyAdST/NPoqVxyva3QjTrKt+tkkFTsmNVMXcmYC +f1xo1/YFp73FFE63VYFI+Yw+Ajau8sYSo4+YvgFCy+Efhf3h3GFDtaiNod56uX9G +9M/cu8XsFzFP2e/0YWY3XL+v7ESOdc3g7yS4FQZ7Z6YvfAed9hCB25cDECvZXqJG +HSYDR38NHyAPROuCwlEwDyVmWRl9bpwZt+hr9kaTQScIDx+rV/EF3o0GKIwtR7AK +jaPAta0f4/Uu+EuWAcccSRUMtfx5/Jse/6iliBvy7JXmA+Y0PrT7K4uHO7iktdI+ +x8WbfZKfnLVuqw5fneTjC1n48Ltjis/f8DgO7BuWTmLdZXddjqqxzBSukFTBn4Hg +/oSg3XiMywOAVrRCNJehcdTG0u/BqZsrRjcYAJaf5qG/0tMLNsuF9Y53XQQAeezE +etL+7y0mkeQhVF+Kmy4= +-----END CERTIFICATE----- diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.key b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.key new file mode 100644 index 0000000000..95e2ef3e8b --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/redis/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCyWXGxJDNNMmU0 +cx9cLZsIhcI+x8KAIGXsVs/PzbyPAmT4RhxeHT5ZMUaSzRbFLBvw1DNrlS3IJQMS +55mP19WPmZYFj3tYK7TVKX/fEKUeZiKxd/LK/bsYp5zs9pI66sKbYkOTIdE26aIl +E5riO143QY4fPDsViwzfvv1O43DXj+C1b54Gsae+CulTt9tS/HjYOLp+0ZhBTAiL +7e4hqpP/+LuS0MxoBAm2QvqFpPNnkk3aoo7lrfj9ZHP8Cm8/DeWkNXxziqhOTcLQ +GD0w/5oOdm2O0sFI30IlbCv6fXVBoyAdOi0Qqw5qEZ2LcnbNjEK8k/QtP6GM3Y71 +pHNIwiwzAgMBAAECgf9REZuCvy2Bi8SoTnjqQuHG5FuA6cPuisuFZr1k88IO+zJQ +uY3WKNs29BV+LcxnoK29W8jQnjqPHXcMfrF5dVWmkrrJdu8JLaGWVHF+uBq8nRb0 +2LvREh5XhZTGzIESNdc/7GIxdouag/8FlzCUYQGuT3v9+wUCiim+4CuIuPvv7ncD +8vANe3Ua5G0mHjVshOiMNpegg45zYlzYpMtUFPs+asLilW6A7UlgC+pLZ1cHUUlU +ZB7KOGT9JdrZpilTidl6LLvDDQK30TSWz8A26SuEAE71DR2VEjLVpjTNS76vlx+c +CrYr/WwpMb0xul+e/uHiNgo+51FiTiJ/IfuGeskCgYEA804CXQM6i5m4/Upps2yG +aTae5xBaYUquZREp5Zb054U6lUAHI41iTMTIwTTvWn5ogNojgi+YjljkzRj2RQ5k +NccBkjBBwwUNVWpBoGeZ73KAdejNB4C4ucGc2kkqEDo4MU5x3IE4JK1Yi1jl9mKb +IR6m3pqb2PCQHjO8sqKNHYkCgYEAu6fH/qUd/XGmCZJWY5K6jg3dISXH16MTO5M+ +jetprkGMMybWKZQa1GedXurPexE48oRlRhkjdQkW6Wcj1Qh6OKp6N2Zx8sY4dLeQ +yVChnMPFE2LK+UlRCKJUZi+rzX415ML6pZg+yW7O2cHpMKv7PlXISw2YDqtboCAi +Y+doqNsCgYBE1yqmBJbZDuqfiCF2KduyA0lcmWzpIEdNw1h2ZIrwwup7dj1O2t8Y +V4lx2TdsBF4vLwli+XKRvCcovMpZaaQC70bLhSnmMxS9uS3OY+HTNTORqQfx+oLJ +1DU8Mf1b0A08LjTbLhijkASAkOuoFehMq66NR3OXIyGz2fGnHYUN+QKBgCC47SL2 +X/hl7PIWVoIef/FtcXXqRKLRiPUGhA3zUwZT38K7rvSpItSPDN4UTAHFywxfEdnb +YFd0Mk6Y8aKgS8+9ynoGnzAaaJXRvKmeKdBQQvlSbNpzcnHy/IylG2xF6dfuOA7Q +MYKmk+Nc8PDPzIveIYMU58MHFn8hm12YaKOpAoGAV1CE8hFkEK9sbRGoKNJkx9nm +CZTv7PybaG/RN4ZrBSwVmnER0FEagA/Tzrlp1pi3sC8ZsC9onSOf6Btq8ZE0zbO1 +vsAm3gTBXcrCJxzw0Wjt8pzEbk3yELm4WE6VDEx4da2jWocdspslpIwdjHnPwsbH +r5O3ZAgigZs/ZtKW/U4= +-----END PRIVATE KEY----- diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java index 5a15429d2f..bc3f47dc37 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.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. @@ -57,7 +57,7 @@ class RedisDockerComposeConnectionDetailsFactory extends DockerComposeConnection RedisDockerComposeConnectionDetails(RunningService service) { super(service); - this.standalone = Standalone.of(service.host(), service.ports().get(REDIS_PORT)); + this.standalone = Standalone.of(service.host(), service.ports().get(REDIS_PORT), getSslBundle(service)); } @Override diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java index 134824058d..ea2095cb3b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.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. @@ -75,7 +75,8 @@ class RedisContainerConnectionDetailsFactory @Override public Standalone getStandalone() { - return Standalone.of(getContainer().getHost(), getContainer().getMappedPort(REDIS_PORT)); + return Standalone.of(getContainer().getHost(), getContainer().getMappedPort(REDIS_PORT), + super.getSslBundle()); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationJedisSslTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationJedisSslTests.java index 228fb8d742..8a1cf57067 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationJedisSslTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationJedisSslTests.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.autoconfigure.data.redis.DataRedisTest; +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.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.container.TestImage; @@ -40,16 +42,15 @@ import static org.assertj.core.api.Assertions.assertThat; */ @Testcontainers(disabledWithoutDocker = true) @ClassPathExclusions("lettuce-core-*.jar") -@DataRedisTest(properties = { "spring.data.redis.ssl.bundle=client", - "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" }) +@DataRedisTest class SampleRedisApplicationJedisSslTests { private static final Charset CHARSET = StandardCharsets.UTF_8; @Container @ServiceConnection + @PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key") + @PemTrustStore("classpath:ssl/test-ca.crt") static RedisContainer redis = TestImage.container(SecureRedisContainer.class); @Autowired diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationReactiveSslTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationReactiveSslTests.java index 3a2d4e56fd..424fa96db0 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationReactiveSslTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationReactiveSslTests.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. @@ -27,6 +27,8 @@ import reactor.test.StepVerifier; 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.redis.core.ReactiveRedisOperations; @@ -37,14 +39,13 @@ import org.springframework.data.redis.core.ReactiveRedisOperations; * @author Scott Frederick */ @Testcontainers(disabledWithoutDocker = true) -@SpringBootTest(properties = { "spring.data.redis.ssl.bundle=client", - "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 class SampleRedisApplicationReactiveSslTests { @Container @ServiceConnection + @PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key") + @PemTrustStore("classpath:ssl/test-ca.crt") static RedisContainer redis = TestImage.container(SecureRedisContainer.class); @Autowired diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationSslTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationSslTests.java index 63733d9d34..91b0607987 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationSslTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-redis/src/dockerTest/java/smoketest/data/redis/SampleRedisApplicationSslTests.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.redis.core.RedisOperations; @@ -38,16 +40,15 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Scott Frederick */ @Testcontainers(disabledWithoutDocker = true) -@SpringBootTest(properties = { "spring.data.redis.ssl.bundle=client", - "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 class SampleRedisApplicationSslTests { private static final Charset CHARSET = StandardCharsets.UTF_8; @Container @ServiceConnection + @PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key") + @PemTrustStore("classpath:ssl/test-ca.crt") static RedisContainer redis = TestImage.container(SecureRedisContainer.class); @Autowired