Skip to content

Commit

Permalink
Merge pull request #46293 from Malandril/mongo-tls-registry
Browse files Browse the repository at this point in the history
Allow mongo client to be configured by a tls registry
  • Loading branch information
geoand authored Feb 19, 2025
2 parents f054455 + bc2b0eb commit 863abd5
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 24 deletions.
31 changes: 25 additions & 6 deletions docs/src/main/asciidoc/mongodb.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]

Expand Down
9 changes: 9 additions & 0 deletions extensions/mongodb-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry-deployment</artifactId>
Expand Down Expand Up @@ -81,6 +85,11 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.certs</groupId>
<artifactId>smallrye-certificate-generator-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RunningMongodProcess> MONGO;
protected TransitionWalker.ReachedState<RunningMongodProcess> mongo;

protected static String getConfiguredConnectionString() {
return getProperty("connection_string");
Expand All @@ -37,7 +38,7 @@ protected static String getProperty(String name) {
}

@BeforeAll
public static void startMongoDatabase() throws IOException {
public void startMongoDatabase() {
forceExtendedSocketOptionsClassInit();

String uri = getConfiguredConnectionString();
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions extensions/mongodb-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mutiny</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny-zero-flow-adapters</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

/**
Expand All @@ -132,6 +137,16 @@ public interface MongoClientConfig {
@WithDefault("false")
boolean tls();

/**
* The name of the TLS configuration to use.
* <p>
* If a name is configured, it uses the configuration from {@code quarkus.tls.<name>.*}
* If a name is configured, but no TLS configuration is found with that name then an error will be thrown.
* <p>
* The default TLS configuration is <strong>not</strong> used by default.
*/
Optional<String> tlsConfigurationName();

/**
* Implies that the hosts given are a seed list, and the driver will attempt to find all members of the set.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -83,6 +86,7 @@ public class MongoClients {
private final MongodbConfig mongodbConfig;
private final MongoClientSupport mongoClientSupport;
private final Instance<CodecProvider> codecProviders;
private final TlsConfigurationRegistry tlsConfigurationRegistry;
private final Instance<PropertyCodecProvider> propertyCodecProviders;
private final Instance<CommandListener> commandListeners;

Expand All @@ -94,6 +98,7 @@ public class MongoClients {

public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientSupport,
Instance<CodecProvider> codecProviders,
TlsConfigurationRegistry tlsConfigurationRegistry,
Instance<PropertyCodecProvider> propertyCodecProviders,
Instance<CommandListener> commandListeners,
Instance<ReactiveContextProvider> reactiveContextProviders,
Expand All @@ -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;
Expand Down Expand Up @@ -206,17 +212,33 @@ public void apply(ConnectionPoolSettings.Builder builder) {
}

private static class SslSettingsBuilder implements Block<SslSettings.Builder> {
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<TlsConfiguration> 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);
}
}
}
}
}

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ static Optional<TlsConfiguration> from(TlsConfigurationRegistry registry, Option
if (name.isPresent()) {
Optional<TlsConfiguration> 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;
}
Expand Down

0 comments on commit 863abd5

Please sign in to comment.