Skip to content

Commit ba2c4d2

Browse files
committed
Add SSL support to auto-configuration for Rabbit Streams
Closes gh-43932 Signed-off-by: Jay Choi <jayyoungchoi22@gmail.com>
1 parent 4f4b93d commit ba2c4d2

File tree

7 files changed

+270
-14
lines changed

7 files changed

+270
-14
lines changed

module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitProperties.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* @author Scott Frederick
5353
* @author Lasse Wulff
5454
* @author Yanming Zhou
55+
* @author Jay Choi
5556
* @since 4.0.0
5657
*/
5758
@ConfigurationProperties("spring.rabbitmq")
@@ -1311,6 +1312,11 @@ public static final class Stream {
13111312
*/
13121313
private @Nullable String name;
13131314

1315+
/**
1316+
* SSL configuration for RabbitMQ instance with the Stream plugin enabled.
1317+
*/
1318+
private final StreamSsl ssl = new StreamSsl();
1319+
13141320
public String getHost() {
13151321
return this.host;
13161322
}
@@ -1359,6 +1365,45 @@ public void setName(@Nullable String name) {
13591365
this.name = name;
13601366
}
13611367

1368+
public StreamSsl getSsl() {
1369+
return this.ssl;
1370+
}
1371+
1372+
public static class StreamSsl {
1373+
1374+
/**
1375+
* Whether to enable SSL support. Enabled automatically if "bundle" is
1376+
* provided.
1377+
*/
1378+
private @Nullable Boolean enabled;
1379+
1380+
/**
1381+
* SSL bundle name.
1382+
*/
1383+
private @Nullable String bundle;
1384+
1385+
public @Nullable Boolean getEnabled() {
1386+
return this.enabled;
1387+
}
1388+
1389+
public boolean determineEnabled() {
1390+
return Boolean.TRUE.equals(getEnabled()) || this.bundle != null;
1391+
}
1392+
1393+
public void setEnabled(@Nullable Boolean enabled) {
1394+
this.enabled = enabled;
1395+
}
1396+
1397+
public @Nullable String getBundle() {
1398+
return this.bundle;
1399+
}
1400+
1401+
public void setBundle(@Nullable String bundle) {
1402+
this.bundle = bundle;
1403+
}
1404+
1405+
}
1406+
13621407
}
13631408

13641409
}

module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConfiguration.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,28 @@
1616

1717
package org.springframework.boot.amqp.autoconfigure;
1818

19+
import javax.net.ssl.SSLException;
20+
1921
import com.rabbitmq.stream.Environment;
2022
import com.rabbitmq.stream.EnvironmentBuilder;
23+
import io.netty.handler.ssl.SslContext;
24+
import io.netty.handler.ssl.SslContextBuilder;
2125
import org.jspecify.annotations.Nullable;
2226

2327
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
2428
import org.springframework.amqp.support.converter.MessageConverter;
2529
import org.springframework.beans.factory.ObjectProvider;
2630
import org.springframework.boot.amqp.autoconfigure.RabbitProperties.Stream;
31+
import org.springframework.boot.amqp.autoconfigure.RabbitProperties.Stream.StreamSsl;
2732
import org.springframework.boot.amqp.autoconfigure.RabbitProperties.StreamContainer;
2833
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2934
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3035
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3136
import org.springframework.boot.context.properties.PropertyMapper;
37+
import org.springframework.boot.ssl.SslBundle;
38+
import org.springframework.boot.ssl.SslBundles;
39+
import org.springframework.boot.ssl.SslManagerBundle;
40+
import org.springframework.boot.ssl.SslOptions;
3241
import org.springframework.context.annotation.Bean;
3342
import org.springframework.context.annotation.Configuration;
3443
import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory;
@@ -41,21 +50,24 @@
4150
import org.springframework.rabbit.stream.producer.RabbitStreamTemplate;
4251
import org.springframework.rabbit.stream.support.converter.StreamMessageConverter;
4352
import org.springframework.util.Assert;
53+
import org.springframework.util.StringUtils;
4454

4555
/**
4656
* Configuration for Spring RabbitMQ Stream plugin support.
4757
*
4858
* @author Gary Russell
4959
* @author Eddú Meléndez
60+
* @author Jay Choi
5061
*/
5162
@Configuration(proxyBeanMethods = false)
5263
@ConditionalOnClass(StreamRabbitListenerContainerFactory.class)
5364
class RabbitStreamConfiguration {
5465

5566
@Bean
5667
@ConditionalOnMissingBean
57-
RabbitStreamConnectionDetails rabbitStreamConnectionDetails(RabbitProperties rabbitProperties) {
58-
return new PropertiesRabbitStreamConnectionDetails(rabbitProperties.getStream());
68+
RabbitStreamConnectionDetails rabbitStreamConnectionDetails(RabbitProperties rabbitProperties,
69+
@Nullable SslBundles sslBundles) {
70+
return new PropertiesRabbitStreamConnectionDetails(rabbitProperties.getStream(), sslBundles);
5971
}
6072

6173
@Bean(name = "rabbitListenerContainerFactory")
@@ -131,15 +143,41 @@ private static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitPr
131143
.to(builder::virtualHost);
132144
map.from(streamConnectionDetails.getUsername()).orFrom(connectionDetails::getUsername).to(builder::username);
133145
map.from(streamConnectionDetails.getPassword()).orFrom(connectionDetails::getPassword).to(builder::password);
146+
SslBundle sslBundle = streamConnectionDetails.getSslBundle();
147+
if (sslBundle != null) {
148+
builder.tls().sslContext(createSslContext(sslBundle));
149+
}
150+
else if (stream.getSsl().determineEnabled()) {
151+
builder.tls();
152+
}
134153
return builder;
135154
}
136155

156+
private static SslContext createSslContext(SslBundle sslBundle) {
157+
SslOptions options = sslBundle.getOptions();
158+
SslManagerBundle managers = sslBundle.getManagers();
159+
try {
160+
return SslContextBuilder.forClient()
161+
.keyManager(managers.getKeyManagerFactory())
162+
.trustManager(managers.getTrustManagerFactory())
163+
.ciphers(SslOptions.asSet(options.getCiphers()))
164+
.protocols(options.getEnabledProtocols())
165+
.build();
166+
}
167+
catch (SSLException ex) {
168+
throw new IllegalStateException("Failed to create SSL context for RabbitMQ Stream", ex);
169+
}
170+
}
171+
137172
static class PropertiesRabbitStreamConnectionDetails implements RabbitStreamConnectionDetails {
138173

139174
private final Stream streamProperties;
140175

141-
PropertiesRabbitStreamConnectionDetails(Stream streamProperties) {
176+
private final @Nullable SslBundles sslBundles;
177+
178+
PropertiesRabbitStreamConnectionDetails(Stream streamProperties, @Nullable SslBundles sslBundles) {
142179
this.streamProperties = streamProperties;
180+
this.sslBundles = sslBundles;
143181
}
144182

145183
@Override
@@ -167,6 +205,19 @@ public int getPort() {
167205
return this.streamProperties.getPassword();
168206
}
169207

208+
@Override
209+
public @Nullable SslBundle getSslBundle() {
210+
StreamSsl ssl = this.streamProperties.getSsl();
211+
if (!ssl.determineEnabled()) {
212+
return null;
213+
}
214+
if (StringUtils.hasLength(ssl.getBundle())) {
215+
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
216+
return this.sslBundles.getBundle(ssl.getBundle());
217+
}
218+
return null;
219+
}
220+
170221
}
171222

172223
}

module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConnectionDetails.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import org.jspecify.annotations.Nullable;
2020

2121
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
22+
import org.springframework.boot.ssl.SslBundle;
2223

2324
/**
2425
* Details required to establish a connection to a RabbitMQ Stream service.
2526
*
2627
* @author Eddú Meléndez
28+
* @author Jay Choi
2729
* @since 4.1.0
2830
*/
2931
public interface RabbitStreamConnectionDetails extends ConnectionDetails {
@@ -64,4 +66,12 @@ public interface RabbitStreamConnectionDetails extends ConnectionDetails {
6466
return null;
6567
}
6668

69+
/**
70+
* SSL bundle to use.
71+
* @return the SSL bundle to use or {@code null}
72+
*/
73+
default @Nullable SslBundle getSslBundle() {
74+
return null;
75+
}
76+
6777
}

module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitStreamDockerComposeConnectionDetailsFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.docker.compose.core.RunningService;
2424
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
2525
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
26+
import org.springframework.boot.ssl.SslBundle;
2627

2728
/**
2829
* {@link DockerComposeConnectionDetailsFactory} to create {@link RabbitConnectionDetails}
@@ -32,6 +33,7 @@
3233
* @author Andy Wilkinson
3334
* @author Phillip Webb
3435
* @author Scott Frederick
36+
* @author Jay Choi
3537
*/
3638
class RabbitStreamDockerComposeConnectionDetailsFactory
3739
extends DockerComposeConnectionDetailsFactory<RabbitStreamConnectionDetails> {
@@ -66,11 +68,14 @@ static class RabbitStreamDockerComposeConnectionDetails extends DockerComposeCon
6668

6769
private final int port;
6870

71+
private final @Nullable SslBundle sslBundle;
72+
6973
protected RabbitStreamDockerComposeConnectionDetails(RunningService service) {
7074
super(service);
7175
this.environment = new RabbitEnvironment(service.env());
7276
this.host = service.host();
7377
this.port = service.ports().get(RABBITMQ_STREAMS_PORT);
78+
this.sslBundle = getSslBundle(service);
7479
}
7580

7681
@Override
@@ -98,6 +103,11 @@ public int getPort() {
98103
return this.port;
99104
}
100105

106+
@Override
107+
public @Nullable SslBundle getSslBundle() {
108+
return this.sslBundle;
109+
}
110+
101111
}
102112

103113
}

module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitStreamContainerConnectionDetailsFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package org.springframework.boot.amqp.testcontainers;
1818

19+
import org.jspecify.annotations.Nullable;
1920
import org.testcontainers.rabbitmq.RabbitMQContainer;
2021

2122
import org.springframework.boot.amqp.autoconfigure.RabbitStreamConnectionDetails;
23+
import org.springframework.boot.ssl.SslBundle;
2224
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
2325
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
2426
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@@ -29,6 +31,7 @@
2931
* {@link ServiceConnection @ServiceConnection}-annotated {@link RabbitMQContainer}.
3032
*
3133
* @author Eddú Meléndez
34+
* @author Jay Choi
3235
*/
3336
class RabbitStreamContainerConnectionDetailsFactory
3437
extends ContainerConnectionDetailsFactory<RabbitMQContainer, RabbitStreamConnectionDetails> {
@@ -81,6 +84,11 @@ public String getPassword() {
8184
return getContainer().getAdminPassword();
8285
}
8386

87+
@Override
88+
public @Nullable SslBundle getSslBundle() {
89+
return super.getSslBundle();
90+
}
91+
8492
}
8593

8694
}

module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitPropertiesTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
* @author Stephane Nicoll
3939
* @author Rafael Carvalho
4040
* @author Scott Frederick
41+
* @author Jay Choi
4142
*/
4243
class RabbitPropertiesTests {
4344

@@ -381,4 +382,34 @@ void hostPropertyMustBeSingleHost() {
381382
.withMessageContaining("spring.rabbitmq.host");
382383
}
383384

385+
@Test
386+
void streamSslIsDisabledByDefault() {
387+
assertThat(this.properties.getStream().getSsl().determineEnabled()).isFalse();
388+
}
389+
390+
@Test
391+
void streamSslIsEnabledWhenEnabledIsTrue() {
392+
this.properties.getStream().getSsl().setEnabled(true);
393+
assertThat(this.properties.getStream().getSsl().determineEnabled()).isTrue();
394+
}
395+
396+
@Test
397+
void streamSslIsEnabledWhenBundleIsSet() {
398+
this.properties.getStream().getSsl().setBundle("test-bundle");
399+
assertThat(this.properties.getStream().getSsl().determineEnabled()).isTrue();
400+
}
401+
402+
@Test
403+
void streamSslIsDisabledWhenEnabledIsFalseAndBundleIsNotSet() {
404+
this.properties.getStream().getSsl().setEnabled(false);
405+
assertThat(this.properties.getStream().getSsl().determineEnabled()).isFalse();
406+
}
407+
408+
@Test
409+
void streamSslIsEnabledWhenBundleIsSetButEnabledIsFalse() {
410+
this.properties.getStream().getSsl().setBundle("test-bundle");
411+
this.properties.getStream().getSsl().setEnabled(false);
412+
assertThat(this.properties.getStream().getSsl().determineEnabled()).isTrue();
413+
}
414+
384415
}

0 commit comments

Comments
 (0)