Skip to content

Commit 863abd5

Browse files
authored
Merge pull request #46293 from Malandril/mongo-tls-registry
Allow mongo client to be configured by a tls registry
2 parents f054455 + bc2b0eb commit 863abd5

File tree

9 files changed

+189
-24
lines changed

9 files changed

+189
-24
lines changed

docs/src/main/asciidoc/mongodb.adoc

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -676,19 +676,38 @@ If you want to use the legacy API, you need to add the following dependency to y
676676
implementation("org.mongodb:mongodb-driver-legacy")
677677
----
678678

679-
== Building a native executable
680-
681-
You can use the MongoDB client in a native executable.
679+
== TLS configuration
682680

683-
If you want to use SSL/TLS encryption, you need to add these properties in your `application.properties`:
681+
If you want to use SSL/TLS encryption, you need to add this property in your `application.properties`:
684682

685683
[source,properties]
686684
----
687685
quarkus.mongodb.tls=true
688-
quarkus.mongodb.tls-insecure=true # only if TLS certificate cannot be validated
689686
----
690687

691-
You can then build a native executable with the usual command:
688+
You can also configure the client to use a tls configuration from the TLS registry, this will enable tls:
689+
690+
[source,properties]
691+
----
692+
quarkus.tls.mongo.trust-store.pem.certs=certa.pem,certb.pem
693+
quarkus.mongodb.tls-configuration-name=mongo
694+
----
695+
696+
You can configure the client to trust servers with certificates with invalid hostnames:
697+
698+
[source,properties]
699+
----
700+
quarkus.tls.mongo.hostname-verification-algorithm=NONE
701+
quarkus.mongodb.tls-configuration-name=mongo
702+
----
703+
704+
705+
== Building a native executable
706+
707+
You can use the MongoDB client in a native executable.
708+
709+
710+
You can build a native executable with the usual command:
692711

693712
include::{includes}/devtools/build-native.adoc[]
694713

extensions/mongodb-client/deployment/pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
<groupId>io.quarkus</groupId>
3535
<artifactId>quarkus-mongodb-client</artifactId>
3636
</dependency>
37+
<dependency>
38+
<groupId>io.quarkus</groupId>
39+
<artifactId>quarkus-tls-registry-deployment</artifactId>
40+
</dependency>
3741
<dependency>
3842
<groupId>io.quarkus</groupId>
3943
<artifactId>quarkus-opentelemetry-deployment</artifactId>
@@ -81,6 +85,11 @@
8185
<artifactId>quarkus-junit5-internal</artifactId>
8286
<scope>test</scope>
8387
</dependency>
88+
<dependency>
89+
<groupId>io.smallrye.certs</groupId>
90+
<artifactId>smallrye-certificate-generator-junit5</artifactId>
91+
<scope>test</scope>
92+
</dependency>
8493
<dependency>
8594
<groupId>de.flapdoodle.embed</groupId>
8695
<artifactId>de.flapdoodle.embed.mongo</artifactId>

extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTestBase.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
package io.quarkus.mongodb;
22

3-
import java.io.IOException;
4-
53
import org.jboss.logging.Logger;
64
import org.junit.jupiter.api.AfterAll;
75
import org.junit.jupiter.api.BeforeAll;
6+
import org.junit.jupiter.api.TestInstance;
87

98
import de.flapdoodle.embed.mongo.commands.MongodArguments;
109
import de.flapdoodle.embed.mongo.config.Net;
1110
import de.flapdoodle.embed.mongo.distribution.Version;
11+
import de.flapdoodle.embed.mongo.transitions.ImmutableMongod;
1212
import de.flapdoodle.embed.mongo.transitions.Mongod;
1313
import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess;
1414
import de.flapdoodle.embed.process.types.ProcessConfig;
1515
import de.flapdoodle.reverse.TransitionWalker;
1616
import de.flapdoodle.reverse.transitions.Start;
1717

18+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
1819
public class MongoTestBase {
1920

2021
private static final Logger LOGGER = Logger.getLogger(MongoTestBase.class);
21-
private static TransitionWalker.ReachedState<RunningMongodProcess> MONGO;
22+
protected TransitionWalker.ReachedState<RunningMongodProcess> mongo;
2223

2324
protected static String getConfiguredConnectionString() {
2425
return getProperty("connection_string");
@@ -37,7 +38,7 @@ protected static String getProperty(String name) {
3738
}
3839

3940
@BeforeAll
40-
public static void startMongoDatabase() throws IOException {
41+
public void startMongoDatabase() {
4142
forceExtendedSocketOptionsClassInit();
4243

4344
String uri = getConfiguredConnectionString();
@@ -47,28 +48,36 @@ public static void startMongoDatabase() throws IOException {
4748
int port = 27018;
4849
LOGGER.infof("Starting Mongo %s on port %s", version, port);
4950

50-
MONGO = Mongod.instance()
51+
ImmutableMongod config = Mongod.instance()
5152
.withNet(Start.to(Net.class).initializedWith(Net.builder()
5253
.from(Net.defaults())
5354
.port(port)
5455
.build()))
5556
.withMongodArguments(Start.to(MongodArguments.class)
56-
.initializedWith(MongodArguments.defaults().withUseNoJournal(false)))
57+
.initializedWith(MongodArguments.defaults()
58+
.withUseNoJournal(
59+
false)))
5760
.withProcessConfig(
5861
Start.to(ProcessConfig.class)
59-
.initializedWith(ProcessConfig.defaults().withStopTimeoutInMillis(15_000)))
60-
.start(version);
62+
.initializedWith(ProcessConfig.defaults()
63+
.withStopTimeoutInMillis(15_000)));
64+
config = addExtraConfig(config);
65+
mongo = config.start(version);
6166

6267
} else {
6368
LOGGER.infof("Using existing Mongo %s", uri);
6469
}
6570
}
6671

72+
protected ImmutableMongod addExtraConfig(ImmutableMongod mongo) {
73+
return mongo;
74+
}
75+
6776
@AfterAll
68-
public static void stopMongoDatabase() {
69-
if (MONGO != null) {
77+
public void stopMongoDatabase() {
78+
if (mongo != null) {
7079
try {
71-
MONGO.close();
80+
mongo.close();
7281
} catch (Exception e) {
7382
LOGGER.error("Unable to stop MongoDB", e);
7483
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.IOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
9+
import jakarta.inject.Inject;
10+
11+
import org.junit.jupiter.api.AfterEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.condition.DisabledOnOs;
14+
import org.junit.jupiter.api.condition.OS;
15+
import org.junit.jupiter.api.extension.RegisterExtension;
16+
17+
import com.mongodb.client.MongoClient;
18+
19+
import de.flapdoodle.embed.mongo.commands.MongodArguments;
20+
import de.flapdoodle.embed.mongo.transitions.ImmutableMongod;
21+
import de.flapdoodle.reverse.transitions.Start;
22+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
23+
import io.quarkus.test.QuarkusUnitTest;
24+
import io.smallrye.certs.Format;
25+
import io.smallrye.certs.junit5.Certificate;
26+
import io.smallrye.certs.junit5.Certificates;
27+
28+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Tests don't pass on windows CI")
29+
@Certificates(baseDir = MongoTlsRegistryTest.BASEDIR, certificates = {
30+
@Certificate(name = "mongo-cert", formats = Format.PEM, client = true)
31+
})
32+
public class MongoTlsRegistryTest extends MongoTestBase {
33+
static final String BASEDIR = "target/certs";
34+
@RegisterExtension
35+
static final QuarkusUnitTest config = new QuarkusUnitTest()
36+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
37+
.withConfigurationResource("tls-mongoclient.properties");
38+
private static final Path BASEPATH = Path.of(BASEDIR);
39+
private final Path serverCertPath = Path.of(BASEDIR, "mongo-cert.crt");
40+
private final Path serverKeyPath = Path.of(BASEDIR, "mongo-cert.key");
41+
private final Path serverCaPath = Path.of(BASEDIR, "mongo-cert-server-ca.crt");
42+
private final Path serverCertKeyPath = Path.of(BASEDIR, "mongo-certkey.pem");
43+
@Inject
44+
MongoClient client;
45+
@Inject
46+
ReactiveMongoClient reactiveClient;
47+
48+
@AfterEach
49+
void cleanup() {
50+
if (reactiveClient != null) {
51+
reactiveClient.close();
52+
}
53+
if (client != null) {
54+
client.close();
55+
}
56+
}
57+
58+
@Override
59+
protected ImmutableMongod addExtraConfig(ImmutableMongod mongo) {
60+
try (var fos = Files.newOutputStream(serverCertKeyPath)) {
61+
Files.copy(serverCertPath, fos);
62+
Files.copy(serverKeyPath, fos);
63+
} catch (IOException e) {
64+
throw new RuntimeException(e);
65+
}
66+
return mongo.withMongodArguments(Start.to(mongo.mongodArguments().destination())
67+
.initializedWith(MongodArguments.builder()
68+
.putArgs("--tlsCertificateKeyFile", serverCertKeyPath.toAbsolutePath().toString())
69+
.putArgs("--tlsMode", "requireTLS")
70+
.putArgs("--tlsCAFile", serverCaPath.toAbsolutePath().toString())
71+
.build()));
72+
73+
}
74+
75+
@Test
76+
public void testClientWorksWithTls() {
77+
assertThat(client.listDatabaseNames().first()).isNotEmpty();
78+
assertThat(reactiveClient.listDatabases().collect().first().await().indefinitely()).isNotEmpty();
79+
}
80+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
quarkus.mongodb.connection-string=mongodb://127.0.0.1:27018
2+
quarkus.mongodb.tls-configuration-name=mongo
3+
quarkus.tls.mongo.key-store.pem.0.cert=target/certs/mongo-cert-client.crt
4+
quarkus.tls.mongo.key-store.pem.0.key=target/certs/mongo-cert-client.key
5+
quarkus.tls.mongo.trust-store.pem.certs=target/certs/mongo-cert-client-ca.crt

extensions/mongodb-client/runtime/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
<groupId>io.quarkus</groupId>
2525
<artifactId>quarkus-mutiny</artifactId>
2626
</dependency>
27+
<dependency>
28+
<groupId>io.quarkus</groupId>
29+
<artifactId>quarkus-tls-registry</artifactId>
30+
</dependency>
2731
<dependency>
2832
<groupId>io.smallrye.reactive</groupId>
2933
<artifactId>mutiny-zero-flow-adapters</artifactId>

extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,13 @@ public interface MongoClientConfig {
122122

123123
/**
124124
* If connecting with TLS, this option enables insecure TLS connections.
125+
*
126+
* @deprecated in favor of configuration at the tls registry level. See {@link #tlsConfigurationName()}
127+
* and quarkus tls registry hostname verification configuration
128+
* {@code quarkus.tls.hostname-verification-algorithm=NONE}.
125129
*/
126130
@WithDefault("false")
131+
@Deprecated(forRemoval = true, since = "3.21")
127132
boolean tlsInsecure();
128133

129134
/**
@@ -132,6 +137,16 @@ public interface MongoClientConfig {
132137
@WithDefault("false")
133138
boolean tls();
134139

140+
/**
141+
* The name of the TLS configuration to use.
142+
* <p>
143+
* If a name is configured, it uses the configuration from {@code quarkus.tls.<name>.*}
144+
* If a name is configured, but no TLS configuration is found with that name then an error will be thrown.
145+
* <p>
146+
* The default TLS configuration is <strong>not</strong> used by default.
147+
*/
148+
Optional<String> tlsConfigurationName();
149+
135150
/**
136151
* Implies that the hosts given are a seed list, and the driver will attempt to find all members of the set.
137152
*/

extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.mongodb.Block;
4141
import com.mongodb.ConnectionString;
4242
import com.mongodb.MongoClientSettings;
43+
import com.mongodb.MongoConfigurationException;
4344
import com.mongodb.MongoCredential;
4445
import com.mongodb.MongoException;
4546
import com.mongodb.ReadConcern;
@@ -65,6 +66,8 @@
6566
import io.quarkus.mongodb.MongoClientName;
6667
import io.quarkus.mongodb.impl.ReactiveMongoClientImpl;
6768
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
69+
import io.quarkus.tls.TlsConfiguration;
70+
import io.quarkus.tls.TlsConfigurationRegistry;
6871
import io.vertx.core.Vertx;
6972
import io.vertx.core.buffer.impl.VertxByteBufAllocator;
7073

@@ -83,6 +86,7 @@ public class MongoClients {
8386
private final MongodbConfig mongodbConfig;
8487
private final MongoClientSupport mongoClientSupport;
8588
private final Instance<CodecProvider> codecProviders;
89+
private final TlsConfigurationRegistry tlsConfigurationRegistry;
8690
private final Instance<PropertyCodecProvider> propertyCodecProviders;
8791
private final Instance<CommandListener> commandListeners;
8892

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

9599
public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientSupport,
96100
Instance<CodecProvider> codecProviders,
101+
TlsConfigurationRegistry tlsConfigurationRegistry,
97102
Instance<PropertyCodecProvider> propertyCodecProviders,
98103
Instance<CommandListener> commandListeners,
99104
Instance<ReactiveContextProvider> reactiveContextProviders,
@@ -102,6 +107,7 @@ public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientS
102107
this.mongodbConfig = mongodbConfig;
103108
this.mongoClientSupport = mongoClientSupport;
104109
this.codecProviders = codecProviders;
110+
this.tlsConfigurationRegistry = tlsConfigurationRegistry;
105111
this.propertyCodecProviders = propertyCodecProviders;
106112
this.commandListeners = commandListeners;
107113
this.reactiveContextProviders = reactiveContextProviders;
@@ -206,17 +212,33 @@ public void apply(ConnectionPoolSettings.Builder builder) {
206212
}
207213

208214
private static class SslSettingsBuilder implements Block<SslSettings.Builder> {
209-
public SslSettingsBuilder(MongoClientConfig config, boolean disableSslSupport) {
210-
this.config = config;
211-
this.disableSslSupport = disableSslSupport;
212-
}
215+
private final TlsConfigurationRegistry tlsConfigurationRegistry;
213216

214217
private final MongoClientConfig config;
215218
private final boolean disableSslSupport;
216219

220+
public SslSettingsBuilder(MongoClientConfig config,
221+
TlsConfigurationRegistry tlsConfigurationRegistry,
222+
boolean disableSslSupport) {
223+
this.config = config;
224+
this.disableSslSupport = disableSslSupport;
225+
this.tlsConfigurationRegistry = tlsConfigurationRegistry;
226+
}
227+
217228
@Override
218229
public void apply(SslSettings.Builder builder) {
219230
builder.enabled(!disableSslSupport).invalidHostNameAllowed(config.tlsInsecure());
231+
if (!disableSslSupport) {
232+
Optional<TlsConfiguration> tlsConfig = TlsConfiguration.from(tlsConfigurationRegistry,
233+
config.tlsConfigurationName());
234+
if (tlsConfig.isPresent()) {
235+
try {
236+
builder.context(tlsConfig.get().createSSLContext());
237+
} catch (Exception e) {
238+
throw new MongoConfigurationException("Could not configure MongoDB client with TLS registry", e);
239+
}
240+
}
241+
}
220242
}
221243
}
222244

@@ -323,8 +345,10 @@ private MongoClientSettings createMongoConfiguration(String name, MongoClientCon
323345
settings.writeConcern(concern);
324346
settings.retryWrites(wc.retryWrites());
325347
}
326-
if (config.tls()) {
327-
settings.applyToSslSettings(new SslSettingsBuilder(config, mongoClientSupport.isDisableSslSupport()));
348+
if (config.tls() || config.tlsConfigurationName().isPresent()) {
349+
settings.applyToSslSettings(new SslSettingsBuilder(config,
350+
tlsConfigurationRegistry,
351+
mongoClientSupport.isDisableSslSupport()));
328352
}
329353
settings.applyToClusterSettings(new ClusterSettingBuilder(config));
330354
settings.applyToConnectionPoolSettings(

extensions/tls-registry/runtime/src/main/java/io/quarkus/tls/TlsConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static Optional<TlsConfiguration> from(TlsConfigurationRegistry registry, Option
1919
if (name.isPresent()) {
2020
Optional<TlsConfiguration> maybeConfiguration = registry.get(name.get());
2121
if (maybeConfiguration.isEmpty()) {
22-
throw new IllegalStateException("Unable to find the TLS configuration for name " + name + ".");
22+
throw new IllegalStateException("Unable to find the TLS configuration for name " + name.get() + ".");
2323
}
2424
return maybeConfiguration;
2525
}

0 commit comments

Comments
 (0)