diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 6371eef039dfbc..e8333a697dff32 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -151,7 +151,7 @@ Refer to the xref:./management-interface-reference.adoc[management interface ref ==== [[ssl]] -== Supporting secure connections with SSL/TLS +== Supporting secure connections with TLS/SSL To have Quarkus support secure connections, you must either provide a certificate and associated key file, or supply a keystore. diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc index f753caaed4bc02..97cd7d8e899ff8 100644 --- a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc +++ b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc @@ -194,6 +194,7 @@ quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret quarkus.http.ssl.client-auth=required <3> quarkus.http.auth.permission.default.paths=/* <4> quarkus.http.auth.permission.default.policy=authenticated +quarkus.http.insecure-requests=disabled <5> ---- <1> The keystore where the server's private key is located. <2> The truststore from which the trusted certificates are loaded. @@ -201,6 +202,7 @@ quarkus.http.auth.permission.default.policy=authenticated To relax this requirement so that the server accepts requests without a certificate, set the value to `REQUEST`. This option is useful when you are also supporting authentication methods other than mTLS. <4> Defines a policy where only authenticated users should have access to resources from your application. +<5> Optionally,explicitly disable the plain HTTP protocol, and consequently require all requests to be made over HTTPS. If `quarkus.http.ssl.client-auth` is set to `required`, the `quarkus.http.insecure-requests` property is automatically set to `disabled`. When the incoming request matches a valid certificate in the truststore, your application can obtain the subject by injecting a `SecurityIdentity` as follows: diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devui/GrpcJsonRPCService.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devui/GrpcJsonRPCService.java index dc4f09e115730c..e8e4defb60b12c 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devui/GrpcJsonRPCService.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devui/GrpcJsonRPCService.java @@ -13,6 +13,8 @@ import io.quarkus.grpc.runtime.config.GrpcConfiguration; import io.quarkus.grpc.runtime.config.GrpcServerConfiguration; import io.quarkus.grpc.runtime.devmode.GrpcServices; +import io.quarkus.vertx.http.runtime.CertificateConfig; +import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -32,6 +34,9 @@ public class GrpcJsonRPCService { @Inject HttpConfiguration httpConfiguration; + @Inject + HttpBuildTimeConfig httpBuildTimeConfig; + @Inject GrpcConfiguration grpcConfiguration; @@ -52,10 +57,16 @@ public void init() { } else { this.host = httpConfiguration.host; this.port = httpConfiguration.port; - this.ssl = httpConfiguration.insecureRequests != HttpConfiguration.InsecureRequests.ENABLED; + this.ssl = isTLSConfigured(httpConfiguration.ssl.certificate); } } + private boolean isTLSConfigured(CertificateConfig certificate) { + return certificate.files.isPresent() + || certificate.keyFiles.isPresent() + || certificate.keyStoreFile.isPresent(); + } + public JsonArray getServices() { JsonArray services = new JsonArray(); List infos = this.grpcServices.getInfos(); diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf index 8a96de4a4137a3..a78a34793096e6 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf +++ b/extensions/vertx-http/deployment/src/test/resources/conf/mtls/mtls-jks.conf @@ -3,6 +3,7 @@ quarkus.http.ssl.certificate.key-store-password=secret quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks quarkus.http.ssl.certificate.trust-store-password=password quarkus.http.ssl.client-auth=REQUIRED +quarkus.http.insecure-requests=enabled quarkus.http.auth.permission.default.paths=/* quarkus.http.auth.permission.default.policy=authenticated \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index c1a2819bd3a88e..5ca439bd79db95 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -28,7 +28,11 @@ public class HttpBuildTimeConfig { /** * Configures the engine to require/request client authentication. - * NONE, REQUEST, REQUIRED + * {@code NONE, REQUEST, REQUIRED}. + *

+ * When set to {@code REQUIRED}, it's recommended to also set `quarkus.http.insecure-requests=disabled` to disable the + * plain HTTP port. If `quarkus.http.insecure-requests` is not set, but this parameter is set to {@code REQUIRED}, then, + * `quarkus.http.insecure-requests` is automatically set to `disabled`. */ @ConfigItem(name = "ssl.client-auth", defaultValue = "NONE") public ClientAuth tlsClientAuth; @@ -43,7 +47,7 @@ public class HttpBuildTimeConfig { /** * A common root path for non-application endpoints. Various extension-provided endpoints such as metrics, health, * and openapi are deployed under this path by default. - * + *

* * Relative path (Default, `q`) -> * Non-application endpoints will be served from * `${quarkus.http.root-path}/${quarkus.http.non-application-root-path}`. @@ -51,7 +55,7 @@ public class HttpBuildTimeConfig { * Non-application endpoints will be served from the specified path. * * `${quarkus.http.root-path}` -> Setting this path to the same value as HTTP root path disables * this root path. All extension-provided endpoints will be served from `${quarkus.http.root-path}`. - * + *

* If the management interface is enabled, the root path for the endpoints exposed on the management interface * is configured using the `quarkus.management.root-path` property instead of this property. * @@ -69,7 +73,7 @@ public class HttpBuildTimeConfig { /** * If enabled then the response body is compressed if the {@code Content-Type} header is set and the value is a compressed * media type as configured via {@link #compressMediaTypes}. - * + *

* Note that the RESTEasy Reactive and Reactive Routes extensions also make it possible to enable/disable compression * declaratively using the annotations {@link io.quarkus.vertx.http.Compressed} and * {@link io.quarkus.vertx.http.Uncompressed}. @@ -79,7 +83,7 @@ public class HttpBuildTimeConfig { /** * When enabled, vert.x will decompress the request's body if it's compressed. - * + *

* Note that the compression format (e.g., gzip) must be specified in the Content-Encoding header * in the request. */ diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java index e726692e1952b5..d7b8c233b5de65 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java @@ -39,9 +39,9 @@ public class HttpConfiguration { /** * The HTTP host - * + *

* In dev/test mode this defaults to localhost, in prod mode this defaults to 0.0.0.0 - * + *

* Defaulting to 0.0.0.0 makes it easier to deploy Quarkus to container, however it * is not suitable for dev/test mode as other people on the network can connect to your * development machine. @@ -86,13 +86,17 @@ public class HttpConfiguration { * then http works as normal. {@code redirect} will still open the http port, but * all requests will be redirected to the HTTPS port. {@code disabled} will prevent the HTTP * port from opening at all. + *

+ * Default is {@code enabled} except when client auth is set to {@code required} (configured using + * {@code quarkus.http.ssl.client-auth=required}). + * In this case, the default is {@code disabled}. */ - @ConfigItem(defaultValue = "enabled") - public InsecureRequests insecureRequests; + @ConfigItem + public Optional insecureRequests; /** * If this is true (the default) then HTTP/2 will be enabled. - * + *

* Note that for browsers to be able to use it HTTPS must be enabled, * and you must be running on JDK11 or above, as JDK8 does not support * ALPN. @@ -134,7 +138,7 @@ public class HttpConfiguration { * The number if IO threads used to perform IO. This will be automatically set to a reasonable value based on * the number of CPU cores if it is not provided. If this is set to a higher value than the number of Vert.x event * loops then it will be capped at the number of event loops. - * + *

* In general this should be controlled by setting quarkus.vertx.event-loops-pool-size, this setting should only * be used if you want to limit the number of HTTP io threads to a smaller number than the total number of IO threads. */ @@ -156,7 +160,6 @@ public class HttpConfiguration { * Http connection read timeout for blocking IO. This is the maximum amount of time * a thread will wait for data, before an IOException will be thrown and the connection * closed. - * */ @ConfigItem(defaultValue = "60s", name = "read-timeout") public Duration readTimeout; @@ -169,7 +172,7 @@ public class HttpConfiguration { /** * The encryption key that is used to store persistent logins (e.g. for form auth). Logins are stored in a persistent * cookie that is encrypted with AES-256 using a key derived from a SHA-256 hash of the key that is provided here. - * + *

* If no key is provided then an in-memory one will be generated, this will change on every restart though so it * is not suitable for production environments. This must be more than 16 characters long for security reasons */ @@ -228,7 +231,7 @@ public class HttpConfiguration { /** * If this is true then the request start time will be recorded to enable logging of total request time. - * + *

* This has a small performance penalty, so is disabled by default. */ @ConfigItem diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index a9d899f379bf26..c440e18cfa213f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -1,26 +1,17 @@ package io.quarkus.vertx.http.runtime; import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe; +import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.getInsecureRequestStrategy; import java.io.File; import java.io.IOException; import java.net.BindException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; +import java.util.function.*; import java.util.regex.Pattern; import jakarta.enterprise.event.Event; @@ -44,12 +35,7 @@ import io.quarkus.netty.runtime.virtual.VirtualAddress; import io.quarkus.netty.runtime.virtual.VirtualChannel; import io.quarkus.netty.runtime.virtual.VirtualServerChannel; -import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.LiveReloadConfig; -import io.quarkus.runtime.QuarkusBindException; -import io.quarkus.runtime.RuntimeValue; -import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.ThreadPoolConfig; +import io.quarkus.runtime.*; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigInstantiator; import io.quarkus.runtime.configuration.ConfigUtils; @@ -75,22 +61,8 @@ import io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers; import io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils; import io.smallrye.common.vertx.VertxContext; -import io.vertx.core.AbstractVerticle; -import io.vertx.core.AsyncResult; -import io.vertx.core.Context; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Handler; -import io.vertx.core.Promise; -import io.vertx.core.Verticle; -import io.vertx.core.Vertx; -import io.vertx.core.http.Cookie; -import io.vertx.core.http.CookieSameSite; -import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.*; +import io.vertx.core.http.*; import io.vertx.core.http.impl.Http1xServerConnection; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.EventLoopContext; @@ -270,6 +242,7 @@ public static void startServerAfterFailedStart() { } rootHandler = root; + var insecureRequestStrategy = getInsecureRequestStrategy(buildConfig, config.insecureRequests); //we can't really do doServerStart(vertx, buildConfig, managementBuildTimeConfig, null, config, managementConfig, LaunchMode.DEVELOPMENT, new Supplier() { @@ -277,7 +250,7 @@ public static void startServerAfterFailedStart() { public Integer get() { return ProcessorInfo.availableProcessors(); //this is dev mode, so the number of IO threads not always being 100% correct does not really matter in this case } - }, null, false); + }, null, insecureRequestStrategy, false); } catch (Exception e) { throw new RuntimeException(e); } @@ -324,8 +297,11 @@ public void startServer(Supplier vertx, ShutdownContext shutdown, || managementConfig.hostEnabled || managementConfig.domainSocketEnabled)) { // Start the server if (closeTask == null) { + var insecureRequestStrategy = getInsecureRequestStrategy(httpBuildTimeConfig, + httpConfiguration.insecureRequests); doServerStart(vertx.get(), httpBuildTimeConfig, managementBuildTimeConfig, managementRouter, httpConfiguration, managementConfig, launchMode, ioThreads, websocketSubProtocols, + insecureRequestStrategy, auxiliaryApplication); if (launchMode != LaunchMode.DEVELOPMENT) { shutdown.addShutdownTask(closeTask); @@ -652,7 +628,8 @@ private static CompletableFuture initializeManagementInterface(Vertx private static CompletableFuture initializeMainHttpServer(Vertx vertx, HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration, LaunchMode launchMode, - Supplier eventLoops, List websocketSubProtocols) throws IOException { + Supplier eventLoops, List websocketSubProtocols, InsecureRequests insecureRequestStrategy) + throws IOException { if (!httpConfiguration.hostEnabled && !httpConfiguration.domainSocketEnabled) { return CompletableFuture.completedFuture(null); @@ -691,9 +668,9 @@ private static CompletableFuture initializeMainHttpServer(Vertx vertx, H } httpMainSslServerOptions = tmpSslConfig; - if (httpConfiguration.insecureRequests != HttpConfiguration.InsecureRequests.ENABLED + if (insecureRequestStrategy != HttpConfiguration.InsecureRequests.ENABLED && httpMainSslServerOptions == null) { - throw new IllegalStateException("Cannot set quarkus.http.redirect-insecure-requests without enabling SSL."); + throw new IllegalStateException("Cannot set quarkus.http.insecure-requests without enabling SSL."); } int eventLoopCount = eventLoops.get(); @@ -713,7 +690,7 @@ private static CompletableFuture initializeMainHttpServer(Vertx vertx, H public Verticle get() { return new WebDeploymentVerticle(httpMainServerOptions, httpMainSslServerOptions, httpMainDomainSocketOptions, launchMode, - httpConfiguration.insecureRequests, httpConfiguration, connectionCount); + insecureRequestStrategy, httpConfiguration, connectionCount); } }, new DeploymentOptions().setInstances(ioThreads), new Handler>() { @Override @@ -725,11 +702,11 @@ public void handle(AsyncResult event) { if ((httpMainSslServerOptions == null) && (httpMainServerOptions != null)) { portsUsed = List.of(httpMainServerOptions.getPort()); - } else if ((httpConfiguration.insecureRequests == InsecureRequests.DISABLED) + } else if ((insecureRequestStrategy == InsecureRequests.DISABLED) && (httpMainSslServerOptions != null)) { portsUsed = List.of(httpMainSslServerOptions.getPort()); } else if ((httpMainSslServerOptions != null) - && (httpConfiguration.insecureRequests == InsecureRequests.ENABLED) + && (insecureRequestStrategy == InsecureRequests.ENABLED) && (httpMainServerOptions != null)) { portsUsed = List.of(httpMainServerOptions.getPort(), httpMainSslServerOptions.getPort()); } @@ -750,10 +727,12 @@ private static void doServerStart(Vertx vertx, HttpBuildTimeConfig httpBuildTime ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, Handler managementRouter, HttpConfiguration httpConfiguration, ManagementInterfaceConfiguration managementConfig, LaunchMode launchMode, - Supplier eventLoops, List websocketSubProtocols, boolean auxiliaryApplication) throws IOException { + Supplier eventLoops, List websocketSubProtocols, + InsecureRequests insecureRequestStrategy, + boolean auxiliaryApplication) throws IOException { var mainServerFuture = initializeMainHttpServer(vertx, httpBuildTimeConfig, httpConfiguration, launchMode, eventLoops, - websocketSubProtocols); + websocketSubProtocols, insecureRequestStrategy); var managementInterfaceFuture = initializeManagementInterface(vertx, managementBuildTimeConfig, managementRouter, managementConfig, launchMode, websocketSubProtocols); var managementInterfaceDomainSocketFuture = initializeManagementInterfaceWithDomainSocket(vertx, @@ -842,18 +821,19 @@ public void handle(AsyncResult event) { throw new RuntimeException("Unable to start HTTP server", e); } - setHttpServerTiming(httpConfiguration.insecureRequests, httpMainServerOptions, httpMainSslServerOptions, + setHttpServerTiming(insecureRequestStrategy == InsecureRequests.DISABLED, httpMainServerOptions, + httpMainSslServerOptions, httpMainDomainSocketOptions, auxiliaryApplication, httpManagementServerOptions); } - private static void setHttpServerTiming(InsecureRequests insecureRequests, HttpServerOptions httpServerOptions, + private static void setHttpServerTiming(boolean httpDisabled, HttpServerOptions httpServerOptions, HttpServerOptions sslConfig, HttpServerOptions domainSocketOptions, boolean auxiliaryApplication, HttpServerOptions managementConfig) { StringBuilder serverListeningMessage = new StringBuilder("Listening on: "); int socketCount = 0; - if (httpServerOptions != null && !InsecureRequests.DISABLED.equals(insecureRequests)) { + if (!httpDisabled && httpServerOptions != null) { serverListeningMessage.append(String.format( "http://%s:%s", httpServerOptions.getHost(), actualHttpPort)); socketCount++; diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java index 36239a6ceaa1f9..2895ca2149df04 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java @@ -12,6 +12,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; +import org.jboss.logging.Logger; + import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.runtime.LaunchMode; @@ -23,6 +25,7 @@ import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceConfiguration; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.ClientAuth; import io.vertx.core.http.Http2Settings; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpVersion; @@ -428,4 +431,25 @@ private static void setIdleTimeout(HttpConfiguration httpConfiguration, HttpServ options.setIdleTimeout(idleTimeout); options.setIdleTimeoutUnit(TimeUnit.MILLISECONDS); } + + public static HttpConfiguration.InsecureRequests getInsecureRequestStrategy(HttpBuildTimeConfig buildTimeConfig, + Optional requests) { + if (requests.isPresent()) { + var value = requests.get(); + if (buildTimeConfig.tlsClientAuth == ClientAuth.REQUIRED && value == HttpConfiguration.InsecureRequests.ENABLED) { + Logger.getLogger(HttpServerOptionsUtils.class).warn( + "When configuring TLS client authentication to be required, it is recommended to **NOT** set `quarkus.http.insecure-requests` to `enabled`. " + + + "You can switch to `redirect` by setting `quarkus.http.insecure-requests=redirect`."); + } + return value; + } + if (buildTimeConfig.tlsClientAuth == ClientAuth.REQUIRED) { + Logger.getLogger(HttpServerOptionsUtils.class).info( + "TLS client authentication is required, thus disabling insecure requests. " + + "You can switch to `redirect` by setting `quarkus.http.insecure-requests=redirect`."); + return HttpConfiguration.InsecureRequests.DISABLED; + } + return HttpConfiguration.InsecureRequests.ENABLED; + } } diff --git a/integration-tests/vertx-http/src/main/resources/application.properties b/integration-tests/vertx-http/src/main/resources/application.properties index 0623305e9666b0..325197e0c00f7e 100644 --- a/integration-tests/vertx-http/src/main/resources/application.properties +++ b/integration-tests/vertx-http/src/main/resources/application.properties @@ -7,6 +7,7 @@ quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks quarkus.http.ssl.certificate.trust-store-password=password quarkus.http.ssl.certificate.trust-store-cert-alias=mykey-1 quarkus.http.ssl.client-auth=REQUIRED +quarkus.http.insecure-requests=ENABLED quarkus.http.access-log.enabled=true quarkus.http.access-log.log-to-file=true quarkus.http.access-log.base-file-name=quarkus-access-log