From fd5fd1491a2804de3a431a6b2f72a2e31bc35dcf Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 18 Apr 2023 14:33:29 -0500 Subject: [PATCH] Add SSL bundle support to MongoDB auto-configuration Update MongoDB auto-configuration so that an SSL can be configured via an SSL bundle. Closes gh-35042 --- .../mongo/MongoAutoConfiguration.java | 5 ++- .../autoconfigure/mongo/MongoProperties.java | 39 ++++++++++++++++++- .../mongo/MongoReactiveAutoConfiguration.java | 5 ++- ...dMongoClientSettingsBuilderCustomizer.java | 22 ++++++++++- .../mongo/MongoAutoConfigurationTests.java | 37 +++++++++++++++++- .../MongoReactiveAutoConfigurationTests.java | 37 +++++++++++++++++- .../src/docs/asciidoc/data/nosql.adoc | 25 ++++++++++++ 7 files changed, 162 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java index 5ba3588a0d7..4c1f06abdd5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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; @@ -69,9 +70,9 @@ public class MongoAutoConfiguration { @Bean StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties, - MongoConnectionDetails connectionDetails) { + MongoConnectionDetails connectionDetails, ObjectProvider sslBundles) { return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(), - properties.getUuidRepresentation()); + properties.getUuidRepresentation(), properties.getSsl(), sslBundles.getIfAvailable()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java index c5b092927de..bb7ae84acc9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. @@ -110,6 +110,8 @@ public class MongoProperties { */ private UuidRepresentation uuidRepresentation = UuidRepresentation.JAVA_LEGACY; + private final Ssl ssl = new Ssl(); + /** * Whether to enable auto-index creation. */ @@ -226,6 +228,10 @@ public class MongoProperties { this.additionalHosts = additionalHosts; } + public Ssl getSsl() { + return this.ssl; + } + public static class Gridfs { /** @@ -256,4 +262,35 @@ public class MongoProperties { } + public static class Ssl { + + /** + * Whether to enable SSL support. Enabled automatically if "bundle" is provided + * unless specified otherwise. + */ + private Boolean enabled; + + /** + * SSL bundle name. + */ + private String bundle; + + public boolean isEnabled() { + return (this.enabled != null) ? this.enabled : this.bundle != null; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java index 12dcd8ef295..1157e002945 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.core.Ordered; @@ -76,9 +77,9 @@ public class MongoReactiveAutoConfiguration { @Bean StandardMongoClientSettingsBuilderCustomizer standardMongoSettingsCustomizer(MongoProperties properties, - MongoConnectionDetails connectionDetails) { + MongoConnectionDetails connectionDetails, ObjectProvider sslBundles) { return new StandardMongoClientSettingsBuilderCustomizer(connectionDetails.getConnectionString(), - properties.getUuidRepresentation()); + properties.getUuidRepresentation(), properties.getSsl(), sslBundles.getIfAvailable()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/StandardMongoClientSettingsBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/StandardMongoClientSettingsBuilderCustomizer.java index 3876cc3a367..0d3478871e1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/StandardMongoClientSettingsBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/StandardMongoClientSettingsBuilderCustomizer.java @@ -18,8 +18,11 @@ package org.springframework.boot.autoconfigure.mongo; import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; +import com.mongodb.connection.SslSettings; import org.bson.UuidRepresentation; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.Ordered; /** @@ -37,18 +40,35 @@ public class StandardMongoClientSettingsBuilderCustomizer implements MongoClient private final UuidRepresentation uuidRepresentation; + private final MongoProperties.Ssl ssl; + + private final SslBundles sslBundles; + private int order = 0; public StandardMongoClientSettingsBuilderCustomizer(ConnectionString connectionString, - UuidRepresentation uuidRepresentation) { + UuidRepresentation uuidRepresentation, MongoProperties.Ssl ssl, SslBundles sslBundles) { this.connectionString = connectionString; this.uuidRepresentation = uuidRepresentation; + this.ssl = ssl; + this.sslBundles = sslBundles; } @Override public void customize(MongoClientSettings.Builder settingsBuilder) { settingsBuilder.uuidRepresentation(this.uuidRepresentation); settingsBuilder.applyConnectionString(this.connectionString); + if (this.ssl.isEnabled()) { + settingsBuilder.applyToSslSettings(this::configureSsl); + } + } + + private void configureSsl(SslSettings.Builder settings) { + settings.enabled(true); + if (this.ssl.getBundle() != null) { + SslBundle sslBundle = this.sslBundles.getBundle(this.ssl.getBundle()); + settings.context(sslBundle.createSslContext()); + } } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java index 2f822731be7..31947cb6824 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfigurationTests.java @@ -23,9 +23,11 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.internal.MongoClientImpl; +import com.mongodb.connection.SslSettings; import org.junit.jupiter.api.Test; 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.context.annotation.Bean; @@ -43,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; class MongoAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, SslAutoConfiguration.class)); @Test void clientExists() { @@ -72,6 +74,39 @@ class MongoAutoConfigurationTests { .run((context) -> assertThat(getSettings(context).getSslSettings().isEnabled()).isTrue()); } + @Test + void configuresSslWhenEnabled() { + this.contextRunner.withPropertyValues("spring.data.mongodb.ssl.enabled=true").run((context) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isTrue(); + assertThat(sslSettings.getContext()).isNull(); + }); + } + + @Test + void configuresSslWithBundle() { + this.contextRunner + .withPropertyValues("spring.data.mongodb.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) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isTrue(); + assertThat(sslSettings.getContext()).isNotNull(); + }); + } + + @Test + void configuresWithoutSslWhenDisabledWithBundle() { + this.contextRunner + .withPropertyValues("spring.data.mongodb.ssl.enabled=false", "spring.data.mongodb.ssl.bundle=test-bundle") + .run((context) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isFalse(); + }); + } + @Test void configuresSingleClient() { this.contextRunner.withUserConfiguration(FallbackMongoClientConfig.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java index d7163e31c1b..44e311be1d7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java @@ -23,6 +23,7 @@ import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; import com.mongodb.ReadPreference; import com.mongodb.connection.AsynchronousSocketChannelStreamFactoryFactory; +import com.mongodb.connection.SslSettings; import com.mongodb.connection.StreamFactory; import com.mongodb.connection.StreamFactoryFactory; import com.mongodb.connection.netty.NettyStreamFactoryFactory; @@ -32,6 +33,7 @@ import io.netty.channel.EventLoopGroup; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -52,7 +54,7 @@ import static org.mockito.Mockito.mock; class MongoReactiveAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MongoReactiveAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(MongoReactiveAutoConfiguration.class, SslAutoConfiguration.class)); @Test void clientExists() { @@ -86,6 +88,39 @@ class MongoReactiveAutoConfigurationTests { }); } + @Test + void configuresSslWhenEnabled() { + this.contextRunner.withPropertyValues("spring.data.mongodb.ssl.enabled=true").run((context) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isTrue(); + assertThat(sslSettings.getContext()).isNull(); + }); + } + + @Test + void configuresSslWithBundle() { + this.contextRunner + .withPropertyValues("spring.data.mongodb.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) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isTrue(); + assertThat(sslSettings.getContext()).isNotNull(); + }); + } + + @Test + void configuresWithoutSslWhenDisabledWithBundle() { + this.contextRunner + .withPropertyValues("spring.data.mongodb.ssl.enabled=false", "spring.data.mongodb.ssl.bundle=test-bundle") + .run((context) -> { + SslSettings sslSettings = getSettings(context).getSslSettings(); + assertThat(sslSettings.isEnabled()).isFalse(); + }); + } + @Test void nettyStreamFactoryFactoryIsConfiguredAutomatically() { AtomicReference eventLoopGroupReference = new AtomicReference<>(); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc index 668248fd42d..f2a4355073a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc @@ -114,6 +114,31 @@ For example, you might declare the following settings in your `application.prope password: "secret" ---- +The auto-configured `MongoClient` can be configured to use SSL for communication with the server by setting the properties as shown in this example: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + spring: + data: + mongodb: + uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" + ssl: + enabled: true +---- + +Custom SSL trust material can be configured in an <> and applied to the `MongoClient` as shown in this example: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + spring: + data: + mongodb: + uri: "mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test" + ssl: + bundle: "example" +---- + + [TIP] ==== If `spring.data.mongodb.port` is not specified, the default of `27017` is used.