Browse Source

Add SSL service connection support for AMQP

See gh-41137
pull/44309/head
Moritz Halbritter 1 year ago
parent
commit
e26ccbe028
  1. 2
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/AbstractConnectionFactoryConfigurer.java
  2. 4
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/CachingConnectionFactoryConfigurer.java
  3. 26
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetails.java
  4. 9
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java
  5. 12
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionDetails.java
  6. 36
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitConnectionFactoryBeanConfigurer.java
  7. 50
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/PropertiesRabbitConnectionDetailsTests.java
  8. 10
      spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java
  9. 23
      spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-amqp/src/dockerTest/java/smoketest/amqp/SampleAmqpSimpleApplicationSslTests.java

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

@ -48,7 +48,7 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn @@ -48,7 +48,7 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
* @param properties the properties to use to configure the connection factory
*/
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties) {
this(properties, new PropertiesRabbitConnectionDetails(properties));
this(properties, new PropertiesRabbitConnectionDetails(properties, null));
}
/**

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

@ -1,5 +1,5 @@ @@ -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.
@ -38,7 +38,7 @@ public class CachingConnectionFactoryConfigurer extends AbstractConnectionFactor @@ -38,7 +38,7 @@ public class CachingConnectionFactoryConfigurer extends AbstractConnectionFactor
* @param properties the properties to use to configure the connection factory
*/
public CachingConnectionFactoryConfigurer(RabbitProperties properties) {
this(properties, new PropertiesRabbitConnectionDetails(properties));
this(properties, new PropertiesRabbitConnectionDetails(properties, null));
}
/**

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

@ -1,5 +1,5 @@ @@ -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.
@ -19,6 +19,12 @@ package org.springframework.boot.autoconfigure.amqp; @@ -19,6 +19,12 @@ package org.springframework.boot.autoconfigure.amqp;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties.Ssl;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Adapts {@link RabbitProperties} to {@link RabbitConnectionDetails}.
*
@ -30,8 +36,11 @@ class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails { @@ -30,8 +36,11 @@ class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
private final RabbitProperties properties;
PropertiesRabbitConnectionDetails(RabbitProperties properties) {
private final SslBundles sslBundles;
PropertiesRabbitConnectionDetails(RabbitProperties properties, SslBundles sslBundles) {
this.properties = properties;
this.sslBundles = sslBundles;
}
@Override
@ -61,4 +70,17 @@ class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails { @@ -61,4 +70,17 @@ class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
return addresses;
}
@Override
public SslBundle getSslBundle() {
Ssl ssl = this.properties.getSsl();
if (!ssl.determineEnabled()) {
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 null;
}
}

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

@ -90,18 +90,17 @@ public class RabbitAutoConfiguration { @@ -90,18 +90,17 @@ public class RabbitAutoConfiguration {
@Bean
@ConditionalOnMissingBean
RabbitConnectionDetails rabbitConnectionDetails() {
return new PropertiesRabbitConnectionDetails(this.properties);
RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider<SslBundles> sslBundles) {
return new PropertiesRabbitConnectionDetails(this.properties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
RabbitConnectionDetails connectionDetails, ObjectProvider<CredentialsProvider> credentialsProvider,
ObjectProvider<CredentialsRefreshService> credentialsRefreshService,
ObjectProvider<SslBundles> sslBundles) {
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
this.properties, connectionDetails, sslBundles.getIfAvailable());
this.properties, connectionDetails);
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
return configurer;

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

@ -1,5 +1,5 @@ @@ -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.autoconfigure.amqp; @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.amqp;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.util.Assert;
/**
@ -73,6 +74,15 @@ public interface RabbitConnectionDetails extends ConnectionDetails { @@ -73,6 +74,15 @@ public interface RabbitConnectionDetails extends ConnectionDetails {
return addresses.get(0);
}
/**
* SSL bundle to use.
* @return the SSL bundle to use
* @since 3.5.0
*/
default SslBundle getSslBundle() {
return null;
}
/**
* A RabbitMQ address.
*

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

@ -48,8 +48,6 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -48,8 +48,6 @@ public class RabbitConnectionFactoryBeanConfigurer {
private final RabbitConnectionDetails connectionDetails;
private final SslBundles sslBundles;
private CredentialsProvider credentialsProvider;
private CredentialsRefreshService credentialsRefreshService;
@ -61,7 +59,7 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -61,7 +59,7 @@ public class RabbitConnectionFactoryBeanConfigurer {
* @param properties the properties
*/
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties) {
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties));
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties, null));
}
/**
@ -96,7 +94,6 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -96,7 +94,6 @@ public class RabbitConnectionFactoryBeanConfigurer {
this.resourceLoader = resourceLoader;
this.rabbitProperties = properties;
this.connectionDetails = connectionDetails;
this.sslBundles = sslBundles;
}
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
@ -129,16 +126,14 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -129,16 +126,14 @@ public class RabbitConnectionFactoryBeanConfigurer {
.asInt(Duration::getSeconds)
.to(factory::setRequestedHeartbeat);
map.from(this.rabbitProperties::getRequestedChannelMax).to(factory::setRequestedChannelMax);
RabbitProperties.Ssl ssl = this.rabbitProperties.getSsl();
if (ssl.determineEnabled()) {
factory.setUseSSL(true);
if (ssl.getBundle() != null) {
SslBundle bundle = this.sslBundles.getBundle(ssl.getBundle());
if (factory instanceof SslBundleRabbitConnectionFactoryBean sslFactory) {
sslFactory.setSslBundle(bundle);
}
}
else {
SslBundle sslBundle = this.connectionDetails.getSslBundle();
if (sslBundle != null) {
applySslBundle(factory, sslBundle);
}
else {
RabbitProperties.Ssl ssl = this.rabbitProperties.getSsl();
if (ssl.determineEnabled()) {
factory.setUseSSL(true);
map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
map.from(ssl::getKeyStore).to(factory::setKeyStore);
@ -148,10 +143,10 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -148,10 +143,10 @@ public class RabbitConnectionFactoryBeanConfigurer {
map.from(ssl::getTrustStore).to(factory::setTrustStore);
map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
map.from(ssl::getTrustStoreAlgorithm).whenNonNull().to(factory::setTrustStoreAlgorithm);
map.from(ssl::isValidateServerCertificate)
.to((validate) -> factory.setSkipServerCertificateValidation(!validate));
map.from(ssl::isVerifyHostname).to(factory::setEnableHostnameVerification);
}
map.from(ssl::isValidateServerCertificate)
.to((validate) -> factory.setSkipServerCertificateValidation(!validate));
map.from(ssl::isVerifyHostname).to(factory::setEnableHostnameVerification);
}
map.from(this.rabbitProperties::getConnectionTimeout)
.whenNonNull()
@ -169,4 +164,11 @@ public class RabbitConnectionFactoryBeanConfigurer { @@ -169,4 +164,11 @@ public class RabbitConnectionFactoryBeanConfigurer {
.to(factory::setMaxInboundMessageBodySize);
}
private static void applySslBundle(RabbitConnectionFactoryBean factory, SslBundle bundle) {
factory.setUseSSL(true);
if (factory instanceof SslBundleRabbitConnectionFactoryBean sslFactory) {
sslFactory.setSslBundle(bundle);
}
}
}

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

@ -1,5 +1,5 @@ @@ -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.
@ -18,11 +18,15 @@ package org.springframework.boot.autoconfigure.amqp; @@ -18,11 +18,15 @@ package org.springframework.boot.autoconfigure.amqp;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address;
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 PropertiesRabbitConnectionDetails}.
@ -33,13 +37,24 @@ class PropertiesRabbitConnectionDetailsTests { @@ -33,13 +37,24 @@ class PropertiesRabbitConnectionDetailsTests {
private static final int DEFAULT_PORT = 5672;
private DefaultSslBundleRegistry sslBundleRegistry;
private RabbitProperties properties;
private PropertiesRabbitConnectionDetails propertiesRabbitConnectionDetails;
@BeforeEach
void setUp() {
this.properties = new RabbitProperties();
this.sslBundleRegistry = new DefaultSslBundleRegistry();
this.propertiesRabbitConnectionDetails = new PropertiesRabbitConnectionDetails(this.properties,
this.sslBundleRegistry);
}
@Test
void getAddresses() {
RabbitProperties properties = new RabbitProperties();
properties.setAddresses(List.of("localhost", "localhost:1234", "[::1]", "[::1]:32863"));
PropertiesRabbitConnectionDetails propertiesRabbitConnectionDetails = new PropertiesRabbitConnectionDetails(
properties);
List<Address> addresses = propertiesRabbitConnectionDetails.getAddresses();
this.properties.setAddresses(List.of("localhost", "localhost:1234", "[::1]", "[::1]:32863"));
List<Address> addresses = this.propertiesRabbitConnectionDetails.getAddresses();
assertThat(addresses.size()).isEqualTo(4);
assertThat(addresses.get(0).host()).isEqualTo("localhost");
assertThat(addresses.get(0).port()).isEqualTo(DEFAULT_PORT);
@ -51,4 +66,27 @@ class PropertiesRabbitConnectionDetailsTests { @@ -51,4 +66,27 @@ class PropertiesRabbitConnectionDetailsTests {
assertThat(addresses.get(3).port()).isEqualTo(32863);
}
@Test
void shouldReturnSslBundle() {
SslBundle bundle1 = mock(SslBundle.class);
this.sslBundleRegistry.registerBundle("bundle-1", bundle1);
this.properties.getSsl().setBundle("bundle-1");
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
assertThat(sslBundle).isSameAs(bundle1);
}
@Test
void shouldReturnNullIfSslIsEnabledButBundleNotSet() {
this.properties.getSsl().setEnabled(true);
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
assertThat(sslBundle).isNull();
}
@Test
void shouldReturnNullIfSslIsNotEnabled() {
this.properties.getSsl().setEnabled(false);
SslBundle sslBundle = this.propertiesRabbitConnectionDetails.getSslBundle();
assertThat(sslBundle).isNull();
}
}

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

@ -1,5 +1,5 @@ @@ -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.
@ -22,6 +22,7 @@ import java.util.List; @@ -22,6 +22,7 @@ import java.util.List;
import org.testcontainers.containers.RabbitMQContainer;
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;
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,10 +67,15 @@ class RabbitContainerConnectionDetailsFactory @@ -66,10 +67,15 @@ class RabbitContainerConnectionDetailsFactory
@Override
public List<Address> getAddresses() {
URI uri = URI.create(getContainer().getAmqpUrl());
URI uri = URI.create((getSslBundle() != null) ? getContainer().getAmqpsUrl() : getContainer().getAmqpUrl());
return List.of(new Address(uri.getHost(), uri.getPort()));
}
@Override
public SslBundle getSslBundle() {
return super.getSslBundle();
}
}
}

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

@ -1,5 +1,5 @@ @@ -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.
@ -28,9 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -28,9 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
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.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
@ -39,25 +40,17 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -39,25 +40,17 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Scott Frederick
*/
@SpringBootTest(properties = { "spring.rabbitmq.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
@Testcontainers(disabledWithoutDocker = true)
@ExtendWith(OutputCaptureExtension.class)
class SampleAmqpSimpleApplicationSslTests {
@Container
@ServiceConnection
@PemKeyStore(certificate = "classpath:ssl/test-client.crt", privateKey = "classpath:ssl/test-client.key")
@PemTrustStore("classpath:ssl/test-ca.crt")
static final SecureRabbitMqContainer rabbit = TestImage.container(SecureRabbitMqContainer.class);
@DynamicPropertySource
static void secureRabbitMqProperties(DynamicPropertyRegistry registry) {
registry.add("spring.rabbitmq.host", rabbit::getHost);
registry.add("spring.rabbitmq.port", rabbit::getAmqpsPort);
registry.add("spring.rabbitmq.username", rabbit::getAdminUsername);
registry.add("spring.rabbitmq.password", rabbit::getAdminPassword);
}
@Autowired
private Sender sender;

Loading…
Cancel
Save