Skip to content

Commit e26ccbe

Browse files
committed
Add SSL service connection support for AMQP
See gh-41137
1 parent b62a0c1 commit e26ccbe

File tree

9 files changed

+121
-51
lines changed

9 files changed

+121
-51
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractConnectionFactoryConfigurer.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
4848
* @param properties the properties to use to configure the connection factory
4949
*/
5050
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties) {
51-
this(properties, new PropertiesRabbitConnectionDetails(properties));
51+
this(properties, new PropertiesRabbitConnectionDetails(properties, null));
5252
}
5353

5454
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/CachingConnectionFactoryConfigurer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ public class CachingConnectionFactoryConfigurer extends AbstractConnectionFactor
3838
* @param properties the properties to use to configure the connection factory
3939
*/
4040
public CachingConnectionFactoryConfigurer(RabbitProperties properties) {
41-
this(properties, new PropertiesRabbitConnectionDetails(properties));
41+
this(properties, new PropertiesRabbitConnectionDetails(properties, null));
4242
}
4343

4444
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetails.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,12 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121

22+
import org.springframework.boot.autoconfigure.amqp.RabbitProperties.Ssl;
23+
import org.springframework.boot.ssl.SslBundle;
24+
import org.springframework.boot.ssl.SslBundles;
25+
import org.springframework.util.Assert;
26+
import org.springframework.util.StringUtils;
27+
2228
/**
2329
* Adapts {@link RabbitProperties} to {@link RabbitConnectionDetails}.
2430
*
@@ -30,8 +36,11 @@ class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
3036

3137
private final RabbitProperties properties;
3238

33-
PropertiesRabbitConnectionDetails(RabbitProperties properties) {
39+
private final SslBundles sslBundles;
40+
41+
PropertiesRabbitConnectionDetails(RabbitProperties properties, SslBundles sslBundles) {
3442
this.properties = properties;
43+
this.sslBundles = sslBundles;
3544
}
3645

3746
@Override
@@ -61,4 +70,17 @@ public List<Address> getAddresses() {
6170
return addresses;
6271
}
6372

73+
@Override
74+
public SslBundle getSslBundle() {
75+
Ssl ssl = this.properties.getSsl();
76+
if (!ssl.determineEnabled()) {
77+
return null;
78+
}
79+
if (StringUtils.hasLength(ssl.getBundle())) {
80+
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
81+
return this.sslBundles.getBundle(ssl.getBundle());
82+
}
83+
return null;
84+
}
85+
6486
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,17 @@ protected RabbitConnectionFactoryCreator(RabbitProperties properties) {
9090

9191
@Bean
9292
@ConditionalOnMissingBean
93-
RabbitConnectionDetails rabbitConnectionDetails() {
94-
return new PropertiesRabbitConnectionDetails(this.properties);
93+
RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider<SslBundles> sslBundles) {
94+
return new PropertiesRabbitConnectionDetails(this.properties, sslBundles.getIfAvailable());
9595
}
9696

9797
@Bean
9898
@ConditionalOnMissingBean
9999
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
100100
RabbitConnectionDetails connectionDetails, ObjectProvider<CredentialsProvider> credentialsProvider,
101-
ObjectProvider<CredentialsRefreshService> credentialsRefreshService,
102-
ObjectProvider<SslBundles> sslBundles) {
101+
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
103102
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
104-
this.properties, connectionDetails, sslBundles.getIfAvailable());
103+
this.properties, connectionDetails);
105104
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
106105
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
107106
return configurer;

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionDetails.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

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

2425
/**
@@ -73,6 +74,15 @@ default Address getFirstAddress() {
7374
return addresses.get(0);
7475
}
7576

77+
/**
78+
* SSL bundle to use.
79+
* @return the SSL bundle to use
80+
* @since 3.5.0
81+
*/
82+
default SslBundle getSslBundle() {
83+
return null;
84+
}
85+
7686
/**
7787
* A RabbitMQ address.
7888
*

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java

+19-17
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ public class RabbitConnectionFactoryBeanConfigurer {
4848

4949
private final RabbitConnectionDetails connectionDetails;
5050

51-
private final SslBundles sslBundles;
52-
5351
private CredentialsProvider credentialsProvider;
5452

5553
private CredentialsRefreshService credentialsRefreshService;
@@ -61,7 +59,7 @@ public class RabbitConnectionFactoryBeanConfigurer {
6159
* @param properties the properties
6260
*/
6361
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties) {
64-
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties));
62+
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties, null));
6563
}
6664

6765
/**
@@ -96,7 +94,6 @@ public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, Rabb
9694
this.resourceLoader = resourceLoader;
9795
this.rabbitProperties = properties;
9896
this.connectionDetails = connectionDetails;
99-
this.sslBundles = sslBundles;
10097
}
10198

10299
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
@@ -129,16 +126,14 @@ public void configure(RabbitConnectionFactoryBean factory) {
129126
.asInt(Duration::getSeconds)
130127
.to(factory::setRequestedHeartbeat);
131128
map.from(this.rabbitProperties::getRequestedChannelMax).to(factory::setRequestedChannelMax);
132-
RabbitProperties.Ssl ssl = this.rabbitProperties.getSsl();
133-
if (ssl.determineEnabled()) {
134-
factory.setUseSSL(true);
135-
if (ssl.getBundle() != null) {
136-
SslBundle bundle = this.sslBundles.getBundle(ssl.getBundle());
137-
if (factory instanceof SslBundleRabbitConnectionFactoryBean sslFactory) {
138-
sslFactory.setSslBundle(bundle);
139-
}
140-
}
141-
else {
129+
SslBundle sslBundle = this.connectionDetails.getSslBundle();
130+
if (sslBundle != null) {
131+
applySslBundle(factory, sslBundle);
132+
}
133+
else {
134+
RabbitProperties.Ssl ssl = this.rabbitProperties.getSsl();
135+
if (ssl.determineEnabled()) {
136+
factory.setUseSSL(true);
142137
map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
143138
map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
144139
map.from(ssl::getKeyStore).to(factory::setKeyStore);
@@ -148,10 +143,10 @@ public void configure(RabbitConnectionFactoryBean factory) {
148143
map.from(ssl::getTrustStore).to(factory::setTrustStore);
149144
map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
150145
map.from(ssl::getTrustStoreAlgorithm).whenNonNull().to(factory::setTrustStoreAlgorithm);
146+
map.from(ssl::isValidateServerCertificate)
147+
.to((validate) -> factory.setSkipServerCertificateValidation(!validate));
148+
map.from(ssl::isVerifyHostname).to(factory::setEnableHostnameVerification);
151149
}
152-
map.from(ssl::isValidateServerCertificate)
153-
.to((validate) -> factory.setSkipServerCertificateValidation(!validate));
154-
map.from(ssl::isVerifyHostname).to(factory::setEnableHostnameVerification);
155150
}
156151
map.from(this.rabbitProperties::getConnectionTimeout)
157152
.whenNonNull()
@@ -169,4 +164,11 @@ public void configure(RabbitConnectionFactoryBean factory) {
169164
.to(factory::setMaxInboundMessageBodySize);
170165
}
171166

167+
private static void applySslBundle(RabbitConnectionFactoryBean factory, SslBundle bundle) {
168+
factory.setUseSSL(true);
169+
if (factory instanceof SslBundleRabbitConnectionFactoryBean sslFactory) {
170+
sslFactory.setSslBundle(bundle);
171+
}
172+
}
173+
172174
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java

+44-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,11 +18,15 @@
1818

1919
import java.util.List;
2020

21+
import org.junit.jupiter.api.BeforeEach;
2122
import org.junit.jupiter.api.Test;
2223

2324
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address;
25+
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
26+
import org.springframework.boot.ssl.SslBundle;
2427

2528
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.mockito.Mockito.mock;
2630

2731
/**
2832
* Tests for {@link PropertiesRabbitConnectionDetails}.
@@ -33,13 +37,24 @@ class PropertiesRabbitConnectionDetailsTests {
3337

3438
private static final int DEFAULT_PORT = 5672;
3539

40+
private DefaultSslBundleRegistry sslBundleRegistry;
41+
42+
private RabbitProperties properties;
43+
44+
private PropertiesRabbitConnectionDetails propertiesRabbitConnectionDetails;
45+
46+
@BeforeEach
47+
void setUp() {
48+
this.properties = new RabbitProperties();
49+
this.sslBundleRegistry = new DefaultSslBundleRegistry();
50+
this.propertiesRabbitConnectionDetails = new PropertiesRabbitConnectionDetails(this.properties,
51+
this.sslBundleRegistry);
52+
}
53+
3654
@Test
3755
void getAddresses() {
38-
RabbitProperties properties = new RabbitProperties();
39-
properties.setAddresses(List.of("localhost", "localhost:1234", "[::1]", "[::1]:32863"));
40-
PropertiesRabbitConnectionDetails propertiesRabbitConnectionDetails = new PropertiesRabbitConnectionDetails(
41-
properties);
42-
List<Address> addresses = propertiesRabbitConnectionDetails.getAddresses();
56+
this.properties.setAddresses(List.of("localhost", "localhost:1234", "[::1]", "[::1]:32863"));
57+
List<Address> addresses = this.propertiesRabbitConnectionDetails.getAddresses();
4358
assertThat(addresses.size()).isEqualTo(4);
4459
assertThat(addresses.get(0).host()).isEqualTo("localhost");
4560
assertThat(addresses.get(0).port()).isEqualTo(DEFAULT_PORT);
@@ -51,4 +66,27 @@ void getAddresses() {
5166
assertThat(addresses.get(3).port()).isEqualTo(32863);
5267
}
5368

69+
@Test
70+
void shouldReturnSslBundle() {
71+
SslBundle bundle1 = mock(SslBundle.class);
72+
this.sslBundleRegistry.registerBundle("bundle-1", bundle1);
73+
this.properties.getSsl().setBundle("bundle-1");
74+
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
75+
assertThat(sslBundle).isSameAs(bundle1);
76+
}
77+
78+
@Test
79+
void shouldReturnNullIfSslIsEnabledButBundleNotSet() {
80+
this.properties.getSsl().setEnabled(true);
81+
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
82+
assertThat(sslBundle).isNull();
83+
}
84+
85+
@Test
86+
void shouldReturnNullIfSslIsNotEnabled() {
87+
this.properties.getSsl().setEnabled(false);
88+
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
89+
assertThat(sslBundle).isNull();
90+
}
91+
5492
}

spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.testcontainers.containers.RabbitMQContainer;
2323

2424
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;
25+
import org.springframework.boot.ssl.SslBundle;
2526
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
2627
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
2728
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@@ -66,10 +67,15 @@ public String getPassword() {
6667

6768
@Override
6869
public List<Address> getAddresses() {
69-
URI uri = URI.create(getContainer().getAmqpUrl());
70+
URI uri = URI.create((getSslBundle() != null) ? getContainer().getAmqpsUrl() : getContainer().getAmqpUrl());
7071
return List.of(new Address(uri.getHost(), uri.getPort()));
7172
}
7273

74+
@Override
75+
public SslBundle getSslBundle() {
76+
return super.getSslBundle();
77+
}
78+
7379
}
7480

7581
}

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-amqp/src/dockerTest/java/smoketest/amqp/SampleAmqpSimpleApplicationSslTests.java

+8-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,9 +28,10 @@
2828
import org.springframework.boot.test.context.SpringBootTest;
2929
import org.springframework.boot.test.system.CapturedOutput;
3030
import org.springframework.boot.test.system.OutputCaptureExtension;
31+
import org.springframework.boot.testcontainers.service.connection.PemKeyStore;
32+
import org.springframework.boot.testcontainers.service.connection.PemTrustStore;
33+
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
3134
import org.springframework.boot.testsupport.container.TestImage;
32-
import org.springframework.test.context.DynamicPropertyRegistry;
33-
import org.springframework.test.context.DynamicPropertySource;
3435

3536
import static org.assertj.core.api.Assertions.assertThat;
3637

@@ -39,25 +40,17 @@
3940
*
4041
* @author Scott Frederick
4142
*/
42-
@SpringBootTest(properties = { "spring.rabbitmq.ssl.bundle=client",
43-
"spring.ssl.bundle.pem.client.keystore.certificate=classpath:ssl/test-client.crt",
44-
"spring.ssl.bundle.pem.client.keystore.private-key=classpath:ssl/test-client.key",
45-
"spring.ssl.bundle.pem.client.truststore.certificate=classpath:ssl/test-ca.crt" })
43+
@SpringBootTest
4644
@Testcontainers(disabledWithoutDocker = true)
4745
@ExtendWith(OutputCaptureExtension.class)
4846
class SampleAmqpSimpleApplicationSslTests {
4947

5048
@Container
49+
@ServiceConnection
50+
@PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key")
51+
@PemTrustStore("classpath:ssl/test-ca.crt")
5152
static final SecureRabbitMqContainer rabbit = TestImage.container(SecureRabbitMqContainer.class);
5253

53-
@DynamicPropertySource
54-
static void secureRabbitMqProperties(DynamicPropertyRegistry registry) {
55-
registry.add("spring.rabbitmq.host", rabbit::getHost);
56-
registry.add("spring.rabbitmq.port", rabbit::getAmqpsPort);
57-
registry.add("spring.rabbitmq.username", rabbit::getAdminUsername);
58-
registry.add("spring.rabbitmq.password", rabbit::getAdminPassword);
59-
}
60-
6154
@Autowired
6255
private Sender sender;
6356

0 commit comments

Comments
 (0)