From bd334f943efc5780883abfc6ee5b2c615a942f55 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 10 Jun 2024 17:22:10 +0300 Subject: [PATCH] Add TLS Registry configuration to WebSockets Next Client Resolves: #41004 --- extensions/websockets-next/runtime/pom.xml | 4 ++ .../next/WebSocketsClientRuntimeConfig.java | 10 +++ .../runtime/BasicWebSocketConnectorImpl.java | 19 ++---- .../next/runtime/WebSocketConnectorBase.java | 66 ++++++++++++++++++- .../next/runtime/WebSocketConnectorImpl.java | 20 ++---- 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/extensions/websockets-next/runtime/pom.xml b/extensions/websockets-next/runtime/pom.xml index d913689652388f..0d2a68bd5bbbc7 100644 --- a/extensions/websockets-next/runtime/pom.xml +++ b/extensions/websockets-next/runtime/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-jackson + + io.quarkus + quarkus-tls-registry + io.quarkus.security diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/WebSocketsClientRuntimeConfig.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/WebSocketsClientRuntimeConfig.java index ecaf0bb169d0d2..b79a9de8578536 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/WebSocketsClientRuntimeConfig.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/WebSocketsClientRuntimeConfig.java @@ -48,4 +48,14 @@ public interface WebSocketsClientRuntimeConfig { @WithDefault("close") UnhandledFailureStrategy unhandledFailureStrategy(); + /** + * 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(); + } diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/BasicWebSocketConnectorImpl.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/BasicWebSocketConnectorImpl.java index 46eca5bd0b36e7..d47df577837d3c 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/BasicWebSocketConnectorImpl.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/BasicWebSocketConnectorImpl.java @@ -14,6 +14,7 @@ import org.jboss.logging.Logger; +import io.quarkus.tls.TlsConfigurationRegistry; import io.quarkus.virtual.threads.VirtualThreadsRecorder; import io.quarkus.websockets.next.BasicWebSocketConnector; import io.quarkus.websockets.next.CloseReason; @@ -27,7 +28,6 @@ import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.WebSocketClient; -import io.vertx.core.http.WebSocketClientOptions; import io.vertx.core.http.WebSocketConnectOptions; @Typed(BasicWebSocketConnector.class) @@ -54,8 +54,8 @@ public class BasicWebSocketConnectorImpl extends WebSocketConnectorBase errorHandler; BasicWebSocketConnectorImpl(Vertx vertx, Codecs codecs, ClientConnectionManager connectionManager, - WebSocketsClientRuntimeConfig config) { - super(vertx, codecs, connectionManager, config); + WebSocketsClientRuntimeConfig config, TlsConfigurationRegistry tlsConfigurationRegistry) { + super(vertx, codecs, connectionManager, config, tlsConfigurationRegistry); } @Override @@ -115,18 +115,7 @@ public Uni connect() { // Currently we create a new client for each connection // The client is closed when the connection is closed // TODO would it make sense to share clients? - WebSocketClientOptions clientOptions = new WebSocketClientOptions(); - if (config.offerPerMessageCompression()) { - clientOptions.setTryUsePerMessageCompression(true); - if (config.compressionLevel().isPresent()) { - clientOptions.setCompressionLevel(config.compressionLevel().getAsInt()); - } - } - if (config.maxMessageSize().isPresent()) { - clientOptions.setMaxMessageSize(config.maxMessageSize().getAsInt()); - } - - WebSocketClient client = vertx.createWebSocketClient(); + WebSocketClient client = vertx.createWebSocketClient(populateClientOptions()); WebSocketConnectOptions connectOptions = new WebSocketConnectOptions() .setSsl(baseUri.getScheme().equals("https")) diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorBase.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorBase.java index 728850f3083fd6..9aa0793d9cc32f 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorBase.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorBase.java @@ -9,13 +9,19 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import io.quarkus.tls.TlsConfiguration; +import io.quarkus.tls.TlsConfigurationRegistry; import io.quarkus.websockets.next.WebSocketClientException; import io.quarkus.websockets.next.WebSocketsClientRuntimeConfig; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.WebSocketClientOptions; +import io.vertx.core.net.SSLOptions; abstract class WebSocketConnectorBase> { @@ -45,8 +51,11 @@ abstract class WebSocketConnectorBase> protected final WebSocketsClientRuntimeConfig config; + protected final TlsConfigurationRegistry tlsConfigurationRegistry; + WebSocketConnectorBase(Vertx vertx, Codecs codecs, - ClientConnectionManager connectionManager, WebSocketsClientRuntimeConfig config) { + ClientConnectionManager connectionManager, WebSocketsClientRuntimeConfig config, + TlsConfigurationRegistry tlsConfigurationRegistry) { this.headers = new HashMap<>(); this.subprotocols = new HashSet<>(); this.pathParams = new HashMap<>(); @@ -54,6 +63,7 @@ abstract class WebSocketConnectorBase> this.codecs = codecs; this.connectionManager = connectionManager; this.config = config; + this.tlsConfigurationRegistry = tlsConfigurationRegistry; this.path = ""; this.pathParamNames = Set.of(); } @@ -129,4 +139,58 @@ String replacePathParameters(String path) { return path.startsWith("/") ? sb.toString() : "/" + sb.toString(); } + protected WebSocketClientOptions populateClientOptions() { + WebSocketClientOptions clientOptions = new WebSocketClientOptions(); + if (config.offerPerMessageCompression()) { + clientOptions.setTryUsePerMessageCompression(true); + if (config.compressionLevel().isPresent()) { + clientOptions.setCompressionLevel(config.compressionLevel().getAsInt()); + } + } + if (config.maxMessageSize().isPresent()) { + clientOptions.setMaxMessageSize(config.maxMessageSize().getAsInt()); + } + + Optional maybeTlsConfiguration = TlsConfiguration.from(tlsConfigurationRegistry, + config.tlsConfigurationName()); + if (maybeTlsConfiguration.isPresent()) { + clientOptions.setSsl(true); + + TlsConfiguration tlsConfiguration = maybeTlsConfiguration.get(); + clientOptions.setTrustOptions(tlsConfiguration.getTrustStoreOptions()); + + if (tlsConfiguration.getTrustStoreOptions() != null) { + clientOptions.setTrustOptions(tlsConfiguration.getTrustStoreOptions()); + } + + // For mTLS: + if (tlsConfiguration.getKeyStoreOptions() != null) { + clientOptions.setKeyCertOptions(tlsConfiguration.getKeyStoreOptions()); + } + + if (tlsConfiguration.isTrustAll()) { + clientOptions.setTrustAll(true); + } + if (tlsConfiguration.getHostnameVerificationAlgorithm().isPresent() + && tlsConfiguration.getHostnameVerificationAlgorithm().get().equals("NONE")) { + // Only disable hostname verification if the algorithm is explicitly set to NONE + clientOptions.setVerifyHost(false); + } + + SSLOptions sslOptions = tlsConfiguration.getSSLOptions(); + if (sslOptions != null) { + clientOptions.setSslHandshakeTimeout(sslOptions.getSslHandshakeTimeout()); + clientOptions.setSslHandshakeTimeoutUnit(sslOptions.getSslHandshakeTimeoutUnit()); + for (String suite : sslOptions.getEnabledCipherSuites()) { + clientOptions.addEnabledCipherSuite(suite); + } + for (Buffer buffer : sslOptions.getCrlValues()) { + clientOptions.addCrlValue(buffer); + } + clientOptions.setEnabledSecureTransportProtocols(sslOptions.getEnabledSecureTransportProtocols()); + clientOptions.setUseAlpn(sslOptions.isUseAlpn()); + } + } + return clientOptions; + } } diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorImpl.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorImpl.java index ceaeab285dd806..be39e41799564c 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorImpl.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketConnectorImpl.java @@ -16,6 +16,7 @@ import org.jboss.logging.Logger; import io.quarkus.arc.Arc; +import io.quarkus.tls.TlsConfigurationRegistry; import io.quarkus.websockets.next.WebSocketClientConnection; import io.quarkus.websockets.next.WebSocketClientException; import io.quarkus.websockets.next.WebSocketConnector; @@ -26,7 +27,6 @@ import io.smallrye.mutiny.vertx.UniHelper; import io.vertx.core.Vertx; import io.vertx.core.http.WebSocketClient; -import io.vertx.core.http.WebSocketClientOptions; import io.vertx.core.http.WebSocketConnectOptions; @Typed(WebSocketConnector.class) @@ -41,8 +41,9 @@ public class WebSocketConnectorImpl extends WebSocketConnectorBase connect() { // Currently we create a new client for each connection // The client is closed when the connection is closed // TODO would it make sense to share clients? - WebSocketClientOptions clientOptions = new WebSocketClientOptions(); - if (config.offerPerMessageCompression()) { - clientOptions.setTryUsePerMessageCompression(true); - if (config.compressionLevel().isPresent()) { - clientOptions.setCompressionLevel(config.compressionLevel().getAsInt()); - } - } - if (config.maxMessageSize().isPresent()) { - clientOptions.setMaxMessageSize(config.maxMessageSize().getAsInt()); - } - - WebSocketClient client = vertx.createWebSocketClient(); + WebSocketClient client = vertx.createWebSocketClient(populateClientOptions()); StringBuilder serverEndpoint = new StringBuilder(); if (baseUri != null) {