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

Add TLS registry support to the Redis client extension #41030

Merged
merged 1 commit into from
Jun 7, 2024
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
14 changes: 13 additions & 1 deletion docs/src/main/asciidoc/redis-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,28 @@

=== Use TLS

To use TLS, you need to:

Check warning on line 248 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 248, "column": 16}}}, "severity": "INFO"}

Check warning on line 248 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 248, "column": 17}}}, "severity": "INFO"}

1. Set the `quarkus.redis.tls.enabled=true` property
1. Set the `quarkus.redis.tls.enabled=true` property or use the xref:./tls-registry-reference.adoc[TLS registry] (recommended)

Check warning on line 250 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 250, "column": 118}}}, "severity": "INFO"}
2. Make sure that your URL starts with `rediss://` (with two `s`)

Check warning on line 251 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'Make sure' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 251, "column": 4}}}, "severity": "WARNING"}


When using the TLS registry, you need to use a named configuration to avoid conflicts with other TLS configurations:

Check warning on line 254 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 254, "column": 5}}}, "severity": "INFO"}

Check warning on line 254 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 254, "column": 34}}}, "severity": "INFO"}

[source,properties]
----
quarkus.tls.redis.trust-store.p12.path=client.p12
quarkus.tls.redis.trust-store.p12.password=secret

quarkus.redis.tls-configuration-name=redis # Reference the named configuration
----

IMPORTANT: The default hostname verifier is set to `NONE`, meaning it does not verify the host name. You can change this behavior by setting the `quarkus.redis.tls.hostname-verification-algorithm` property, to `HTTPS` for example.

Check warning on line 264 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'hostname' rather than 'host name' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'hostname' rather than 'host name' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 264, "column": 91}}}, "severity": "WARNING"}


=== Configure the authentication

Check warning on line 267 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 267, "column": 1}}}, "severity": "INFO"}

The Redis password can be set in the `redis://` URL or with the `quarkus.redis.password` property.

Check warning on line 269 in docs/src/main/asciidoc/redis-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'.", "location": {"path": "docs/src/main/asciidoc/redis-reference.adoc", "range": {"start": {"line": 269, "column": 92}}}, "severity": "INFO"}
We recommend the latter, and if possible, using secrets or an environment variable to configure the password.

The associated environment variable is `QUARKUS_REDIS_PASSWORD`, or `QUARKUS_REDIS_<NAME>_PASSWORD` for named clients.
Expand Down
4 changes: 4 additions & 0 deletions extensions/redis-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;
import io.quarkus.tls.TlsRegistryBuildItem;
import io.quarkus.vertx.deployment.VertxBuildItem;
import io.vertx.redis.client.impl.types.BulkType;

Expand Down Expand Up @@ -127,7 +128,8 @@ public void init(
VertxBuildItem vertxBuildItem,
ApplicationArchivesBuildItem applicationArchivesBuildItem, LaunchModeBuildItem launchMode,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles) {
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
TlsRegistryBuildItem tlsRegistryBuildItem) {

// Collect the used redis clients, the unused clients will not be instantiated.
Set<String> names = new HashSet<>();
Expand Down Expand Up @@ -156,7 +158,7 @@ public void init(
.ifPresent(x -> names.addAll(configuredClientNames(buildTimeConfig, ConfigProvider.getConfig())));

// Inject the creation of the client when the application starts.
recorder.initialize(vertxBuildItem.getVertx(), names);
recorder.initialize(vertxBuildItem.getVertx(), names, tlsRegistryBuildItem.registry());

// Create the supplier and define the beans.
for (String name : names) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.codecs.Codec;
import io.quarkus.redis.runtime.client.RedisClientRecorder;
import io.quarkus.tls.TlsRegistryBuildItem;
import io.quarkus.vertx.deployment.VertxBuildItem;

public class RedisDatasourceProcessor {
Expand Down Expand Up @@ -84,7 +85,8 @@ public void init(RedisClientRecorder recorder,
List<RequestedRedisClientBuildItem> clients,
ShutdownContextBuildItem shutdown,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
VertxBuildItem vertxBuildItem) {
VertxBuildItem vertxBuildItem,
TlsRegistryBuildItem tlsRegistryBuildItem) {

if (clients.isEmpty()) {
return;
Expand All @@ -94,7 +96,7 @@ public void init(RedisClientRecorder recorder,
names.add(client.name);
}
// Inject the creation of the client when the application starts.
recorder.initialize(vertxBuildItem.getVertx(), names);
recorder.initialize(vertxBuildItem.getVertx(), names, tlsRegistryBuildItem.registry());

// Create the supplier and define the beans.
for (String name : names) {
Expand Down
4 changes: 4 additions & 0 deletions extensions/redis-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-redis-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.redis.client.Command;
import io.vertx.mutiny.redis.client.Redis;
Expand All @@ -48,7 +49,8 @@ public RedisClientRecorder(RedisConfig rc) {
this.config = rc;
}

public void initialize(RuntimeValue<io.vertx.core.Vertx> vertx, Set<String> names) {
public void initialize(RuntimeValue<io.vertx.core.Vertx> vertx, Set<String> names,
Supplier<TlsConfigurationRegistry> registry) {
Instance<ObservableRedisMetrics> instance = CDI.current().select(ObservableRedisMetrics.class);
if (instance.isResolvable()) {
this.metrics = instance.get();
Expand All @@ -58,9 +60,11 @@ public void initialize(RuntimeValue<io.vertx.core.Vertx> vertx, Set<String> name

this.vertx = Vertx.newInstance(vertx.getValue());

TlsConfigurationRegistry tlsRegistry = registry.get();

_registerCodecs();

_initialize(vertx.getValue(), names);
_initialize(vertx.getValue(), names, tlsRegistry);
}

private static void _registerCodecs() {
Expand All @@ -69,7 +73,7 @@ private static void _registerCodecs() {
Codecs.register(codecs.stream());
}

public void _initialize(io.vertx.core.Vertx vertx, Set<String> names) {
public void _initialize(io.vertx.core.Vertx vertx, Set<String> names, TlsConfigurationRegistry tlsRegistry) {
for (String name : names) {
// Search if we have an associated config:
// - if default -> Default
Expand All @@ -89,11 +93,12 @@ public ConfigurationException get() {
}
});
clients.computeIfAbsent(name,
x -> new RedisClientAndApi(name, VertxRedisClientFactory.create(name, vertx, actualConfig), metrics));
x -> new RedisClientAndApi(name, VertxRedisClientFactory.create(name, vertx, actualConfig, tlsRegistry),
metrics));
} else if (DEFAULT_CLIENT_NAME.equalsIgnoreCase(name) && maybe.isPresent()) {
clients.computeIfAbsent(name,
x -> new RedisClientAndApi(name,
VertxRedisClientFactory.create(DEFAULT_CLIENT_NAME, vertx, maybe.get()), metrics));
VertxRedisClientFactory.create(DEFAULT_CLIENT_NAME, vertx, maybe.get(), tlsRegistry), metrics));
}
// Do not throw an error. We would need to check if the default redis client is used.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.jboss.logging.Logger;

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InjectableInstance;
Expand All @@ -20,12 +23,15 @@
import io.quarkus.redis.client.RedisOptionsCustomizer;
import io.quarkus.redis.runtime.client.config.NetConfig;
import io.quarkus.redis.runtime.client.config.RedisClientConfig;
import io.quarkus.redis.runtime.client.config.TlsConfig;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.smallrye.common.annotation.Identifier;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.SSLOptions;
import io.vertx.redis.client.Redis;
import io.vertx.redis.client.RedisClientType;
import io.vertx.redis.client.RedisOptions;
Expand All @@ -37,11 +43,13 @@ public class VertxRedisClientFactory {

public static final String DEFAULT_CLIENT = "<default>";

private static final Logger LOGGER = Logger.getLogger(VertxRedisClientFactory.class);

private VertxRedisClientFactory() {
// Avoid direct instantiation.
}

public static Redis create(String name, Vertx vertx, RedisClientConfig config) {
public static Redis create(String name, Vertx vertx, RedisClientConfig config, TlsConfigurationRegistry tlsRegistry) {
RedisOptions options = new RedisOptions();

List<URI> hosts = new ArrayList<>();
Expand Down Expand Up @@ -86,6 +94,7 @@ public static Redis create(String name, Vertx vertx, RedisClientConfig config) {
config.replicas().ifPresent(options::setUseReplicas);

options.setNetClientOptions(toNetClientOptions(config));
configureTLS(name, config, tlsRegistry, options.getNetClientOptions());

options.setPoolName(name);
// Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with redis.
Expand All @@ -110,35 +119,14 @@ private static void customize(String name, RedisOptions options) {

private static NetClientOptions toNetClientOptions(RedisClientConfig config) {
NetConfig tcp = config.tcp();
TlsConfig tls = config.tls();
NetClientOptions net = new NetClientOptions();

tcp.alpn().ifPresent(net::setUseAlpn);
tcp.applicationLayerProtocols().ifPresent(net::setApplicationLayerProtocols);
tcp.connectionTimeout().ifPresent(d -> net.setConnectTimeout((int) d.toMillis()));

String verificationAlgorithm = tls.hostnameVerificationAlgorithm();
if ("NONE".equalsIgnoreCase(verificationAlgorithm)) {
net.setHostnameVerificationAlgorithm("");
} else {
net.setHostnameVerificationAlgorithm(verificationAlgorithm);
}

tcp.idleTimeout().ifPresent(d -> net.setIdleTimeout((int) d.toSeconds()));

tcp.keepAlive().ifPresent(b -> net.setTcpKeepAlive(true));
tcp.noDelay().ifPresent(b -> net.setTcpNoDelay(true));

net.setSsl(tls.enabled()).setTrustAll(tls.trustAll());

configurePemTrustOptions(net, tls.trustCertificatePem());
configureJksTrustOptions(net, tls.trustCertificateJks());
configurePfxTrustOptions(net, tls.trustCertificatePfx());

configurePemKeyCertOptions(net, tls.keyCertificatePem());
configureJksKeyCertOptions(net, tls.keyCertificateJks());
configurePfxKeyCertOptions(net, tls.keyCertificatePfx());

net.setReconnectAttempts(config.reconnectAttempts());
net.setReconnectInterval(config.reconnectInterval().toMillis());

Expand Down Expand Up @@ -191,4 +179,82 @@ public static RedisHostsProvider findProvider(String name) {
return providers.get();
}

private static void configureTLS(String name, RedisClientConfig config, TlsConfigurationRegistry tlsRegistry,
NetClientOptions net) {
TlsConfiguration configuration = null;
boolean defaultTrustAll = false;

// Check if we have a named TLS configuration or a default configuration:
if (config.tlsConfigurationName().isPresent()) {
Optional<TlsConfiguration> maybeConfiguration = tlsRegistry.get(config.tlsConfigurationName().get());
if (maybeConfiguration.isEmpty()) {
throw new IllegalStateException("Unable to find the TLS configuration "
+ config.tlsConfigurationName().get() + " for the Redis client " + name + ".");
}
configuration = maybeConfiguration.get();
} else if (tlsRegistry.getDefault().isPresent() && (tlsRegistry.getDefault().get().isTrustAll())) {
defaultTrustAll = tlsRegistry.getDefault().get().isTrustAll();
if (defaultTrustAll) {
LOGGER.warn("The default TLS configuration is set to trust all certificates. This is a security risk."
+ "Please use a named TLS configuration for the Redis client " + name + " to avoid this warning.");
}
}

// Apply the configuration
if (configuration != null) {
// This part is often the same (or close) for every Vert.x client:
net.setSsl(true);

if (configuration.getTrustStoreOptions() != null) {
net.setTrustOptions(configuration.getTrustStoreOptions());
}

// For mTLS:
if (configuration.getKeyStoreOptions() != null) {
net.setKeyCertOptions(configuration.getKeyStoreOptions());
}

if (configuration.isTrustAll()) {
net.setTrustAll(true);
}
if (configuration.getHostnameVerificationAlgorithm().isPresent()) {
net.setHostnameVerificationAlgorithm(configuration.getHostnameVerificationAlgorithm().get());
}

SSLOptions sslOptions = configuration.getSSLOptions();
if (sslOptions != null) {
net.setSslHandshakeTimeout(sslOptions.getSslHandshakeTimeout());
net.setSslHandshakeTimeoutUnit(sslOptions.getSslHandshakeTimeoutUnit());
for (String suite : sslOptions.getEnabledCipherSuites()) {
net.addEnabledCipherSuite(suite);
}
for (Buffer buffer : sslOptions.getCrlValues()) {
net.addCrlValue(buffer);
}
net.setEnabledSecureTransportProtocols(sslOptions.getEnabledSecureTransportProtocols());
net.setUseAlpn(sslOptions.isUseAlpn());
}

} else {
config.tcp().alpn().ifPresent(net::setUseAlpn);

String verificationAlgorithm = config.tls().hostnameVerificationAlgorithm();
if ("NONE".equalsIgnoreCase(verificationAlgorithm)) {
net.setHostnameVerificationAlgorithm("");
} else {
net.setHostnameVerificationAlgorithm(verificationAlgorithm);
}
net.setSsl(config.tls().enabled() || defaultTrustAll);
net.setTrustAll(config.tls().trustAll() || defaultTrustAll);

configurePemTrustOptions(net, config.tls().trustCertificatePem());
configureJksTrustOptions(net, config.tls().trustCertificateJks());
configurePfxTrustOptions(net, config.tls().trustCertificatePfx());

configurePemKeyCertOptions(net, config.tls().keyCertificatePem());
configureJksKeyCertOptions(net, config.tls().keyCertificateJks());
configurePfxKeyCertOptions(net, config.tls().keyCertificatePfx());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,18 @@ public interface RedisClientConfig {
@ConfigDocSection
TlsConfig 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>
* If no TLS configuration name is set then, {@code quarkus.redis.$client-name.tls} will be used.
* <p>
* The default TLS configuration is <strong>not</strong> used by default.
*/
Optional<String> tlsConfigurationName();

default String toDebugString() {
return "RedisClientConfig{" +
"hosts=" + hosts() +
Expand Down
Loading