From bc2b0eb2d2b360be448f9ef69608a34b6ac55e85 Mon Sep 17 00:00:00 2001 From: Thomas Canava Date: Sat, 15 Feb 2025 17:51:41 +0100 Subject: [PATCH] feat: Add tls registry to mongo client test: Disable tls test on windows Apply suggestions from code review Co-authored-by: Guillaume Smet Deprecate tlsInsecure mongo configuration Review comments, enable tls when configuration name set --- docs/src/main/asciidoc/mongodb.adoc | 31 +++++-- extensions/mongodb-client/deployment/pom.xml | 9 +++ .../io/quarkus/mongodb/MongoTestBase.java | 31 ++++--- .../quarkus/mongodb/MongoTlsRegistryTest.java | 80 +++++++++++++++++++ .../test/resources/tls-mongoclient.properties | 5 ++ extensions/mongodb-client/runtime/pom.xml | 4 + .../mongodb/runtime/MongoClientConfig.java | 15 ++++ .../quarkus/mongodb/runtime/MongoClients.java | 36 +++++++-- .../java/io/quarkus/tls/TlsConfiguration.java | 2 +- 9 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTlsRegistryTest.java create mode 100644 extensions/mongodb-client/deployment/src/test/resources/tls-mongoclient.properties diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 9ce7be3dc23df..280618e112431 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -676,19 +676,38 @@ If you want to use the legacy API, you need to add the following dependency to y implementation("org.mongodb:mongodb-driver-legacy") ---- -== Building a native executable - -You can use the MongoDB client in a native executable. +== TLS configuration -If you want to use SSL/TLS encryption, you need to add these properties in your `application.properties`: +If you want to use SSL/TLS encryption, you need to add this property in your `application.properties`: [source,properties] ---- quarkus.mongodb.tls=true -quarkus.mongodb.tls-insecure=true # only if TLS certificate cannot be validated ---- -You can then build a native executable with the usual command: +You can also configure the client to use a tls configuration from the TLS registry, this will enable tls: + +[source,properties] +---- +quarkus.tls.mongo.trust-store.pem.certs=certa.pem,certb.pem +quarkus.mongodb.tls-configuration-name=mongo +---- + +You can configure the client to trust servers with certificates with invalid hostnames: + +[source,properties] +---- +quarkus.tls.mongo.hostname-verification-algorithm=NONE +quarkus.mongodb.tls-configuration-name=mongo +---- + + +== Building a native executable + +You can use the MongoDB client in a native executable. + + +You can build a native executable with the usual command: include::{includes}/devtools/build-native.adoc[] diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 3bffa4334d1e8..2e1330c5b210e 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -34,6 +34,10 @@ io.quarkus quarkus-mongodb-client + + io.quarkus + quarkus-tls-registry-deployment + io.quarkus quarkus-opentelemetry-deployment @@ -81,6 +85,11 @@ quarkus-junit5-internal test + + io.smallrye.certs + smallrye-certificate-generator-junit5 + test + de.flapdoodle.embed de.flapdoodle.embed.mongo diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTestBase.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTestBase.java index c7359fa000824..4804c75682bc5 100644 --- a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTestBase.java +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTestBase.java @@ -1,24 +1,25 @@ package io.quarkus.mongodb; -import java.io.IOException; - import org.jboss.logging.Logger; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; import de.flapdoodle.embed.mongo.commands.MongodArguments; import de.flapdoodle.embed.mongo.config.Net; import de.flapdoodle.embed.mongo.distribution.Version; +import de.flapdoodle.embed.mongo.transitions.ImmutableMongod; import de.flapdoodle.embed.mongo.transitions.Mongod; import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess; import de.flapdoodle.embed.process.types.ProcessConfig; import de.flapdoodle.reverse.TransitionWalker; import de.flapdoodle.reverse.transitions.Start; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MongoTestBase { private static final Logger LOGGER = Logger.getLogger(MongoTestBase.class); - private static TransitionWalker.ReachedState MONGO; + protected TransitionWalker.ReachedState mongo; protected static String getConfiguredConnectionString() { return getProperty("connection_string"); @@ -37,7 +38,7 @@ protected static String getProperty(String name) { } @BeforeAll - public static void startMongoDatabase() throws IOException { + public void startMongoDatabase() { forceExtendedSocketOptionsClassInit(); String uri = getConfiguredConnectionString(); @@ -47,28 +48,36 @@ public static void startMongoDatabase() throws IOException { int port = 27018; LOGGER.infof("Starting Mongo %s on port %s", version, port); - MONGO = Mongod.instance() + ImmutableMongod config = Mongod.instance() .withNet(Start.to(Net.class).initializedWith(Net.builder() .from(Net.defaults()) .port(port) .build())) .withMongodArguments(Start.to(MongodArguments.class) - .initializedWith(MongodArguments.defaults().withUseNoJournal(false))) + .initializedWith(MongodArguments.defaults() + .withUseNoJournal( + false))) .withProcessConfig( Start.to(ProcessConfig.class) - .initializedWith(ProcessConfig.defaults().withStopTimeoutInMillis(15_000))) - .start(version); + .initializedWith(ProcessConfig.defaults() + .withStopTimeoutInMillis(15_000))); + config = addExtraConfig(config); + mongo = config.start(version); } else { LOGGER.infof("Using existing Mongo %s", uri); } } + protected ImmutableMongod addExtraConfig(ImmutableMongod mongo) { + return mongo; + } + @AfterAll - public static void stopMongoDatabase() { - if (MONGO != null) { + public void stopMongoDatabase() { + if (mongo != null) { try { - MONGO.close(); + mongo.close(); } catch (Exception e) { LOGGER.error("Unable to stop MongoDB", e); } diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTlsRegistryTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTlsRegistryTest.java new file mode 100644 index 0000000000000..b524737d8caa2 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTlsRegistryTest.java @@ -0,0 +1,80 @@ +package io.quarkus.mongodb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.client.MongoClient; + +import de.flapdoodle.embed.mongo.commands.MongodArguments; +import de.flapdoodle.embed.mongo.transitions.ImmutableMongod; +import de.flapdoodle.reverse.transitions.Start; +import io.quarkus.mongodb.reactive.ReactiveMongoClient; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.certs.Format; +import io.smallrye.certs.junit5.Certificate; +import io.smallrye.certs.junit5.Certificates; + +@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Tests don't pass on windows CI") +@Certificates(baseDir = MongoTlsRegistryTest.BASEDIR, certificates = { + @Certificate(name = "mongo-cert", formats = Format.PEM, client = true) +}) +public class MongoTlsRegistryTest extends MongoTestBase { + static final String BASEDIR = "target/certs"; + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class)) + .withConfigurationResource("tls-mongoclient.properties"); + private static final Path BASEPATH = Path.of(BASEDIR); + private final Path serverCertPath = Path.of(BASEDIR, "mongo-cert.crt"); + private final Path serverKeyPath = Path.of(BASEDIR, "mongo-cert.key"); + private final Path serverCaPath = Path.of(BASEDIR, "mongo-cert-server-ca.crt"); + private final Path serverCertKeyPath = Path.of(BASEDIR, "mongo-certkey.pem"); + @Inject + MongoClient client; + @Inject + ReactiveMongoClient reactiveClient; + + @AfterEach + void cleanup() { + if (reactiveClient != null) { + reactiveClient.close(); + } + if (client != null) { + client.close(); + } + } + + @Override + protected ImmutableMongod addExtraConfig(ImmutableMongod mongo) { + try (var fos = Files.newOutputStream(serverCertKeyPath)) { + Files.copy(serverCertPath, fos); + Files.copy(serverKeyPath, fos); + } catch (IOException e) { + throw new RuntimeException(e); + } + return mongo.withMongodArguments(Start.to(mongo.mongodArguments().destination()) + .initializedWith(MongodArguments.builder() + .putArgs("--tlsCertificateKeyFile", serverCertKeyPath.toAbsolutePath().toString()) + .putArgs("--tlsMode", "requireTLS") + .putArgs("--tlsCAFile", serverCaPath.toAbsolutePath().toString()) + .build())); + + } + + @Test + public void testClientWorksWithTls() { + assertThat(client.listDatabaseNames().first()).isNotEmpty(); + assertThat(reactiveClient.listDatabases().collect().first().await().indefinitely()).isNotEmpty(); + } +} diff --git a/extensions/mongodb-client/deployment/src/test/resources/tls-mongoclient.properties b/extensions/mongodb-client/deployment/src/test/resources/tls-mongoclient.properties new file mode 100644 index 0000000000000..6c9b9fd977826 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/resources/tls-mongoclient.properties @@ -0,0 +1,5 @@ +quarkus.mongodb.connection-string=mongodb://127.0.0.1:27018 +quarkus.mongodb.tls-configuration-name=mongo +quarkus.tls.mongo.key-store.pem.0.cert=target/certs/mongo-cert-client.crt +quarkus.tls.mongo.key-store.pem.0.key=target/certs/mongo-cert-client.key +quarkus.tls.mongo.trust-store.pem.certs=target/certs/mongo-cert-client-ca.crt \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml index 71f2a5190f6fd..af44d9db8b5f2 100644 --- a/extensions/mongodb-client/runtime/pom.xml +++ b/extensions/mongodb-client/runtime/pom.xml @@ -24,6 +24,10 @@ io.quarkus quarkus-mutiny + + io.quarkus + quarkus-tls-registry + io.smallrye.reactive mutiny-zero-flow-adapters diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java index a6c481b2ab185..7ae7a604590c0 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java @@ -122,8 +122,13 @@ public interface MongoClientConfig { /** * If connecting with TLS, this option enables insecure TLS connections. + * + * @deprecated in favor of configuration at the tls registry level. See {@link #tlsConfigurationName()} + * and quarkus tls registry hostname verification configuration + * {@code quarkus.tls.hostname-verification-algorithm=NONE}. */ @WithDefault("false") + @Deprecated(forRemoval = true, since = "3.21") boolean tlsInsecure(); /** @@ -132,6 +137,16 @@ public interface MongoClientConfig { @WithDefault("false") boolean tls(); + /** + * The name of the TLS configuration to use. + *

+ * If a name is configured, it uses the configuration from {@code quarkus.tls..*} + * If a name is configured, but no TLS configuration is found with that name then an error will be thrown. + *

+ * The default TLS configuration is not used by default. + */ + Optional tlsConfigurationName(); + /** * Implies that the hosts given are a seed list, and the driver will attempt to find all members of the set. */ diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java index 2adb842ee8195..99f62f74dba5e 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java @@ -40,6 +40,7 @@ import com.mongodb.Block; import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoConfigurationException; import com.mongodb.MongoCredential; import com.mongodb.MongoException; import com.mongodb.ReadConcern; @@ -65,6 +66,8 @@ import io.quarkus.mongodb.MongoClientName; import io.quarkus.mongodb.impl.ReactiveMongoClientImpl; import io.quarkus.mongodb.reactive.ReactiveMongoClient; +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.TlsConfigurationRegistry; import io.vertx.core.Vertx; import io.vertx.core.buffer.impl.VertxByteBufAllocator; @@ -83,6 +86,7 @@ public class MongoClients { private final MongodbConfig mongodbConfig; private final MongoClientSupport mongoClientSupport; private final Instance codecProviders; + private final TlsConfigurationRegistry tlsConfigurationRegistry; private final Instance propertyCodecProviders; private final Instance commandListeners; @@ -94,6 +98,7 @@ public class MongoClients { public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientSupport, Instance codecProviders, + TlsConfigurationRegistry tlsConfigurationRegistry, Instance propertyCodecProviders, Instance commandListeners, Instance reactiveContextProviders, @@ -102,6 +107,7 @@ public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientS this.mongodbConfig = mongodbConfig; this.mongoClientSupport = mongoClientSupport; this.codecProviders = codecProviders; + this.tlsConfigurationRegistry = tlsConfigurationRegistry; this.propertyCodecProviders = propertyCodecProviders; this.commandListeners = commandListeners; this.reactiveContextProviders = reactiveContextProviders; @@ -206,17 +212,33 @@ public void apply(ConnectionPoolSettings.Builder builder) { } private static class SslSettingsBuilder implements Block { - public SslSettingsBuilder(MongoClientConfig config, boolean disableSslSupport) { - this.config = config; - this.disableSslSupport = disableSslSupport; - } + private final TlsConfigurationRegistry tlsConfigurationRegistry; private final MongoClientConfig config; private final boolean disableSslSupport; + public SslSettingsBuilder(MongoClientConfig config, + TlsConfigurationRegistry tlsConfigurationRegistry, + boolean disableSslSupport) { + this.config = config; + this.disableSslSupport = disableSslSupport; + this.tlsConfigurationRegistry = tlsConfigurationRegistry; + } + @Override public void apply(SslSettings.Builder builder) { builder.enabled(!disableSslSupport).invalidHostNameAllowed(config.tlsInsecure()); + if (!disableSslSupport) { + Optional tlsConfig = TlsConfiguration.from(tlsConfigurationRegistry, + config.tlsConfigurationName()); + if (tlsConfig.isPresent()) { + try { + builder.context(tlsConfig.get().createSSLContext()); + } catch (Exception e) { + throw new MongoConfigurationException("Could not configure MongoDB client with TLS registry", e); + } + } + } } } @@ -323,8 +345,10 @@ private MongoClientSettings createMongoConfiguration(String name, MongoClientCon settings.writeConcern(concern); settings.retryWrites(wc.retryWrites()); } - if (config.tls()) { - settings.applyToSslSettings(new SslSettingsBuilder(config, mongoClientSupport.isDisableSslSupport())); + if (config.tls() || config.tlsConfigurationName().isPresent()) { + settings.applyToSslSettings(new SslSettingsBuilder(config, + tlsConfigurationRegistry, + mongoClientSupport.isDisableSslSupport())); } settings.applyToClusterSettings(new ClusterSettingBuilder(config)); settings.applyToConnectionPoolSettings( diff --git a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java index 2046ec9d8caad..0252df2d030b6 100644 --- a/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java +++ b/extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java @@ -19,7 +19,7 @@ static Optional from(TlsConfigurationRegistry registry, Option if (name.isPresent()) { Optional maybeConfiguration = registry.get(name.get()); if (maybeConfiguration.isEmpty()) { - throw new IllegalStateException("Unable to find the TLS configuration for name " + name + "."); + throw new IllegalStateException("Unable to find the TLS configuration for name " + name.get() + "."); } return maybeConfiguration; }