Add SSL bundle support to Rabbit auto-configuration
This commit is contained in:
@@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
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.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -69,6 +70,7 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@@ -97,9 +99,10 @@ public class RabbitAutoConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
|
||||
RabbitConnectionDetails connectionDetails, ObjectProvider<CredentialsProvider> credentialsProvider,
|
||||
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
|
||||
ObjectProvider<CredentialsRefreshService> credentialsRefreshService,
|
||||
ObjectProvider<SslBundles> sslBundles) {
|
||||
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
|
||||
this.properties, connectionDetails);
|
||||
this.properties, connectionDetails, sslBundles.getIfAvailable());
|
||||
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
|
||||
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
|
||||
return configurer;
|
||||
@@ -122,7 +125,7 @@ public class RabbitAutoConfiguration {
|
||||
CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer,
|
||||
ObjectProvider<ConnectionFactoryCustomizer> connectionFactoryCustomizers) throws Exception {
|
||||
|
||||
RabbitConnectionFactoryBean connectionFactoryBean = new RabbitConnectionFactoryBean();
|
||||
RabbitConnectionFactoryBean connectionFactoryBean = new SslBundleRabbitConnectionFactoryBean();
|
||||
rabbitConnectionFactoryBeanConfigurer.configure(connectionFactoryBean);
|
||||
connectionFactoryBean.afterPropertiesSet();
|
||||
com.rabbitmq.client.ConnectionFactory connectionFactory = connectionFactoryBean.getObject();
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.rabbitmq.client.impl.CredentialsRefreshService;
|
||||
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.util.unit.DataSize;
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class RabbitConnectionFactoryBeanConfigurer {
|
||||
@@ -45,6 +48,8 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
|
||||
private final RabbitConnectionDetails connectionDetails;
|
||||
|
||||
private final SslBundles sslBundles;
|
||||
|
||||
private CredentialsProvider credentialsProvider;
|
||||
|
||||
private CredentialsRefreshService credentialsRefreshService;
|
||||
@@ -65,17 +70,33 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
* priority over the properties.
|
||||
* @param resourceLoader the resource loader
|
||||
* @param properties the properties
|
||||
* @param connectionDetails the connection details.
|
||||
* @param connectionDetails the connection details
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties,
|
||||
RabbitConnectionDetails connectionDetails) {
|
||||
this(resourceLoader, properties, connectionDetails, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will use the given {@code resourceLoader},
|
||||
* {@code properties}, {@code connectionDetails}, and {@code sslBundles}. The
|
||||
* connection details have priority over the properties.
|
||||
* @param resourceLoader the resource loader
|
||||
* @param properties the properties
|
||||
* @param connectionDetails the connection details
|
||||
* @param sslBundles the SSL bundles
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties,
|
||||
RabbitConnectionDetails connectionDetails, SslBundles sslBundles) {
|
||||
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
|
||||
Assert.notNull(properties, "Properties must not be null");
|
||||
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.rabbitProperties = properties;
|
||||
this.connectionDetails = connectionDetails;
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
|
||||
@@ -111,15 +132,23 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
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);
|
||||
map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
|
||||
map.from(ssl::getKeyStoreAlgorithm).whenNonNull().to(factory::setKeyStoreAlgorithm);
|
||||
map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
|
||||
map.from(ssl::getTrustStore).to(factory::setTrustStore);
|
||||
map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
|
||||
map.from(ssl::getTrustStoreAlgorithm).whenNonNull().to(factory::setTrustStoreAlgorithm);
|
||||
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);
|
||||
map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
|
||||
map.from(ssl::getKeyStoreAlgorithm).whenNonNull().to(factory::setKeyStoreAlgorithm);
|
||||
map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
|
||||
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::getVerifyHostname).to(factory::setEnableHostnameVerification);
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.util.unit.DataSize;
|
||||
* @author Franjo Zilic
|
||||
* @author Eddú Meléndez
|
||||
* @author Rafael Carvalho
|
||||
* @author Scott Frederick
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.rabbitmq")
|
||||
@@ -400,6 +401,11 @@ public class RabbitProperties {
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* SSL bundle name.
|
||||
*/
|
||||
private String bundle;
|
||||
|
||||
/**
|
||||
* Path to the key store that holds the SSL certificate.
|
||||
*/
|
||||
@@ -467,7 +473,7 @@ public class RabbitProperties {
|
||||
* @see #getEnabled() ()
|
||||
*/
|
||||
public boolean determineEnabled() {
|
||||
boolean defaultEnabled = Optional.ofNullable(getEnabled()).orElse(false);
|
||||
boolean defaultEnabled = Optional.ofNullable(getEnabled()).orElse(false) || this.bundle != null;
|
||||
if (CollectionUtils.isEmpty(RabbitProperties.this.parsedAddresses)) {
|
||||
return defaultEnabled;
|
||||
}
|
||||
@@ -479,6 +485,14 @@ public class RabbitProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getBundle() {
|
||||
return this.bundle;
|
||||
}
|
||||
|
||||
public void setBundle(String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
public String getKeyStore() {
|
||||
return this.keyStore;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.amqp;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
|
||||
/**
|
||||
* A {@link RabbitConnectionFactoryBean} that can be configured with custom SSL trust
|
||||
* material from an {@link SslBundle}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class SslBundleRabbitConnectionFactoryBean extends RabbitConnectionFactoryBean {
|
||||
|
||||
private SslBundle sslBundle;
|
||||
|
||||
private boolean enableHostnameVerification;
|
||||
|
||||
@Override
|
||||
protected void setUpSSL() {
|
||||
if (this.sslBundle != null) {
|
||||
this.connectionFactory.useSslProtocol(this.sslBundle.createSslContext());
|
||||
if (this.enableHostnameVerification) {
|
||||
this.connectionFactory.enableHostnameVerification();
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.setUpSSL();
|
||||
}
|
||||
}
|
||||
|
||||
void setSslBundle(SslBundle sslBundle) {
|
||||
this.sslBundle = sslBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableHostnameVerification(boolean enable) {
|
||||
this.enableHostnameVerification = enable;
|
||||
super.setEnableHostnameVerification(enable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,6 +60,7 @@ import org.springframework.amqp.rabbit.retry.MessageRecoverer;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
@@ -102,12 +103,13 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class RabbitAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void testDefaultRabbitConfiguration() {
|
||||
@@ -777,6 +779,16 @@ class RabbitAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableSslWithInvalidSslBundleFails() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
.withPropertyValues("spring.rabbitmq.ssl.bundle=invalid")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context).getFailure().hasMessageContaining("SSL bundle name 'invalid' cannot be found");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// Make sure that we at least attempt to load the store
|
||||
void enableSslWithNonExistingKeystoreShouldFail() {
|
||||
@@ -827,6 +839,19 @@ class RabbitAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableSslWithBundle() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
.withPropertyValues("spring.rabbitmq.ssl.bundle=test-bundle",
|
||||
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:test.jks",
|
||||
"spring.ssl.bundle.jks.test-bundle.keystore.password=secret",
|
||||
"spring.ssl.bundle.jks.test-bundle.key.password=password")
|
||||
.run((context) -> {
|
||||
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory(context);
|
||||
assertThat(rabbitConnectionFactory.isSSL()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableSslWithKeystoreTypeAndTrustStoreTypeShouldWork() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
|
||||
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Rafael Carvalho
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class RabbitPropertiesTests {
|
||||
|
||||
@@ -321,6 +322,12 @@ class RabbitPropertiesTests {
|
||||
assertThat(this.properties.getSsl().determineEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void determineSslEnabledIsTrueWhenBundleIsSetAndNoAddresses() {
|
||||
this.properties.getSsl().setBundle("test");
|
||||
assertThat(this.properties.getSsl().determineEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertiesUseConsistentDefaultValues() {
|
||||
ConnectionFactory connectionFactory = new ConnectionFactory();
|
||||
|
||||
Reference in New Issue
Block a user