Add SSL service connection support for AMQP
See gh-41137
This commit is contained in:
parent
b62a0c1ae0
commit
e26ccbe028
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
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 {
|
||||
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.
|
||||
*
|
||||
|
@ -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 {
|
||||
* @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 {
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.rabbitProperties = properties;
|
||||
this.connectionDetails = connectionDetails;
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
|
||||
@ -129,16 +126,14 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
.asInt(Duration::getSeconds)
|
||||
.to(factory::setRequestedHeartbeat);
|
||||
map.from(this.rabbitProperties::getRequestedChannelMax).to(factory::setRequestedChannelMax);
|
||||
SslBundle sslBundle = this.connectionDetails.getSslBundle();
|
||||
if (sslBundle != null) {
|
||||
applySslBundle(factory, sslBundle);
|
||||
}
|
||||
else {
|
||||
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 {
|
||||
map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
|
||||
map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
|
||||
map.from(ssl::getKeyStore).to(factory::setKeyStore);
|
||||
@ -148,11 +143,11 @@ 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(this.rabbitProperties::getConnectionTimeout)
|
||||
.whenNonNull()
|
||||
.asInt(Duration::toMillis)
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
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
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
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;
|
||||
*
|
||||
* @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…
x
Reference in New Issue
Block a user