Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow mongo client to be configured by a tls registry #46293

Merged
merged 1 commit into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests on windows CI don't seem to work correctly, I cannot reproduce it, on my local windows.
It may be because the generated certs use relative paths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the report. It might be something related to the Junit 5 plugin generating certificates.

@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
Loading