diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java index c9573b4fee..ff3ec81253 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.amqp; import java.time.Duration; import java.util.List; +import java.util.Objects; import com.rabbitmq.client.Channel; @@ -41,6 +42,7 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.util.ReflectionUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitTemplate}. @@ -91,6 +93,11 @@ public class RabbitAutoConfiguration { @ConditionalOnMissingBean(ConnectionFactory.class) protected static class RabbitConnectionFactoryCreator { + // Only available in rabbitmq-java-client 5.4.0 + + private static final boolean CAN_ENABLE_HOSTNAME_VERIFICATION = ReflectionUtils + .findMethod(com.rabbitmq.client.ConnectionFactory.class, + "enableHostnameVerification") != null; + @Bean public CachingConnectionFactory rabbitConnectionFactory( RabbitProperties properties, @@ -140,6 +147,13 @@ public class RabbitAutoConfiguration { map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType); map.from(ssl::getTrustStore).to(factory::setTrustStore); map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase); + map.from(ssl::isValidateServerCertificate).to((validate) -> factory + .setSkipServerCertificateValidation(!validate)); + map.from(ssl::getVerifyHostname).when(Objects::nonNull) + .to(factory::setEnableHostnameVerification); + if (ssl.getVerifyHostname() == null && CAN_ENABLE_HOSTNAME_VERIFICATION) { + factory.setEnableHostnameVerification(true); + } } map.from(properties::getConnectionTimeout).whenNonNull() .asInt(Duration::toMillis).to(factory::setConnectionTimeout); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java index 02292ebecc..44a9157f3a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java @@ -350,6 +350,17 @@ public class RabbitProperties { */ private String algorithm; + /** + * Whether to enable server side certificate validation. + */ + private boolean validateServerCertificate = true; + + /** + * Whether to enable hostname verification. Requires AMQP client 4.8 or above and + * defaults to true when a suitable client version is used. + */ + private Boolean verifyHostname; + public boolean isEnabled() { return this.enabled; } @@ -414,6 +425,22 @@ public class RabbitProperties { this.algorithm = sslAlgorithm; } + public boolean isValidateServerCertificate() { + return this.validateServerCertificate; + } + + public void setValidateServerCertificate(boolean validateServerCertificate) { + this.validateServerCertificate = validateServerCertificate; + } + + public Boolean getVerifyHostname() { + return this.verifyHostname; + } + + public void setVerifyHostname(Boolean verifyHostname) { + this.verifyHostname = verifyHostname; + } + } public static class Cache { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java index 37ef790aab..73b66d3ede 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java @@ -19,10 +19,14 @@ package org.springframework.boot.autoconfigure.amqp; import java.security.NoSuchAlgorithmException; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; import com.rabbitmq.client.Address; import com.rabbitmq.client.Connection; +import com.rabbitmq.client.SslContextFactory; +import com.rabbitmq.client.TrustEverythingTrustManager; import org.aopalliance.aop.Advice; import org.junit.Rule; import org.junit.Test; @@ -764,6 +768,45 @@ public class RabbitAutoConfigurationTests { .run((context) -> assertThat(context).hasNotFailed()); } + @Test + public void enableSslWithValidateServerCertificateFalse() throws Exception { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.rabbitmq.ssl.enabled:true", + "spring.rabbitmq.ssl.validateServerCertificate=false") + .run((context) -> { + com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory( + context); + TrustManager trustManager = getTrustManager(rabbitConnectionFactory); + assertThat(trustManager) + .isInstanceOf(TrustEverythingTrustManager.class); + }); + } + + @Test + public void enableSslWithValidateServerCertificateDefault() throws Exception { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .withPropertyValues("spring.rabbitmq.ssl.enabled:true").run((context) -> { + com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = getTargetConnectionFactory( + context); + TrustManager trustManager = getTrustManager(rabbitConnectionFactory); + assertThat(trustManager) + .isNotInstanceOf(TrustEverythingTrustManager.class); + }); + } + + private TrustManager getTrustManager( + com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory) { + SslContextFactory sslContextFactory = (SslContextFactory) ReflectionTestUtils + .getField(rabbitConnectionFactory, "sslContextFactory"); + SSLContext sslContext = sslContextFactory.create("connection"); + Object spi = ReflectionTestUtils.getField(sslContext, "contextSpi"); + Object trustManager = ReflectionTestUtils.getField(spi, "trustManager"); + while (trustManager.getClass().getName().endsWith("Wrapper")) { + trustManager = ReflectionTestUtils.getField(trustManager, "tm"); + } + return (TrustManager) trustManager; + } + private com.rabbitmq.client.ConnectionFactory getTargetConnectionFactory( AssertableApplicationContext context) { CachingConnectionFactory connectionFactory = context diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 06a47ea13c..5a9c101c4e 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -156,7 +156,7 @@ 1.23 7.4.0 5.1.0.BUILD-SNAPSHOT - 2.1.0.M2 + 2.1.0.BUILD-SNAPSHOT 4.1.0.M3 2.0.2.RELEASE Lovelace-RC2 diff --git a/spring-boot-samples/spring-boot-sample-amqp/pom.xml b/spring-boot-samples/spring-boot-sample-amqp/pom.xml index 9cd849c0d4..c6a66a6100 100644 --- a/spring-boot-samples/spring-boot-sample-amqp/pom.xml +++ b/spring-boot-samples/spring-boot-sample-amqp/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -13,6 +14,7 @@ Spring Boot AMQP Sample ${basedir}/../.. + 5.4.0