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

Automatically disable the HTTP port when client certs are required #37324

Merged
merged 1 commit into from
Dec 6, 2023
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
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
====

[[ssl]]
== Supporting secure connections with SSL/TLS
== Supporting secure connections with TLS/SSL

Check warning on line 154 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.CaseSensitiveTerms] Use 'SSL/TLS' rather than 'SSL'. Raw Output: {"message": "[Quarkus.CaseSensitiveTerms] Use 'SSL/TLS' rather than 'SSL'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 154, "column": 43}}}, "severity": "INFO"}

To have Quarkus support secure connections, you must either provide a certificate and associated key file, or supply a keystore.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,15 @@
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>
sberyozkin marked this conversation as resolved.
Show resolved Hide resolved
----
<1> The keystore where the server's private key is located.
<2> The truststore from which the trusted certificates are loaded.

Check warning on line 200 in docs/src/main/asciidoc/security-authentication-mechanisms.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/security-authentication-mechanisms.adoc", "range": {"start": {"line": 200, "column": 24}}}, "severity": "INFO"}
<3> With this value set to `required`, the server requires certificates from clients.
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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +34,9 @@ public class GrpcJsonRPCService {
@Inject
HttpConfiguration httpConfiguration;

@Inject
HttpBuildTimeConfig httpBuildTimeConfig;

@Inject
GrpcConfiguration grpcConfiguration;

Expand All @@ -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<GrpcServices.ServiceDefinitionAndStatus> infos = this.grpcServices.getInfos();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ public class HttpBuildTimeConfig {

/**
* Configures the engine to require/request client authentication.
* NONE, REQUEST, REQUIRED
* {@code NONE, REQUEST, REQUIRED}.
* <p>
* 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;
Expand All @@ -43,15 +47,15 @@ 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.
*
* <p>
* * Relative path (Default, `q`) ->
* Non-application endpoints will be served from
* `${quarkus.http.root-path}/${quarkus.http.non-application-root-path}`.
* * Absolute path (`/q`) ->
* 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}`.
*
* <p>
* 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.
*
Expand All @@ -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}.
*
* <p>
* 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}.
Expand All @@ -79,7 +83,7 @@ public class HttpBuildTimeConfig {

/**
* When enabled, vert.x will decompress the request's body if it's compressed.
*
* <p>
* Note that the compression format (e.g., gzip) must be specified in the Content-Encoding header
* in the request.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public class HttpConfiguration {

/**
* The HTTP host
*
* <p>
* In dev/test mode this defaults to localhost, in prod mode this defaults to 0.0.0.0
*
* <p>
* 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.
Expand Down Expand Up @@ -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.
* <p>
* 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> insecureRequests;

/**
* If this is true (the default) then HTTP/2 will be enabled.
*
* <p>
* 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.
Expand Down Expand Up @@ -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.
*
* <p>
* 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.
*/
Expand All @@ -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;
Expand All @@ -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.
*
* <p>
* 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
*/
Expand Down Expand Up @@ -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.
*
* <p>
* This has a small performance penalty, so is disabled by default.
*/
@ConfigItem
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -270,14 +242,15 @@ 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<Integer>() {
@Override
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);
}
Expand Down Expand Up @@ -324,8 +297,11 @@ public void startServer(Supplier<Vertx> 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);
Expand Down Expand Up @@ -652,7 +628,8 @@ private static CompletableFuture<HttpServer> initializeManagementInterface(Vertx
private static CompletableFuture<String> initializeMainHttpServer(Vertx vertx, HttpBuildTimeConfig httpBuildTimeConfig,
HttpConfiguration httpConfiguration,
LaunchMode launchMode,
Supplier<Integer> eventLoops, List<String> websocketSubProtocols) throws IOException {
Supplier<Integer> eventLoops, List<String> websocketSubProtocols, InsecureRequests insecureRequestStrategy)
throws IOException {

if (!httpConfiguration.hostEnabled && !httpConfiguration.domainSocketEnabled) {
return CompletableFuture.completedFuture(null);
Expand Down Expand Up @@ -691,9 +668,9 @@ private static CompletableFuture<String> 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();
Expand All @@ -713,7 +690,7 @@ private static CompletableFuture<String> 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<AsyncResult<String>>() {
@Override
Expand All @@ -725,11 +702,11 @@ public void handle(AsyncResult<String> 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());
}
Expand All @@ -750,10 +727,12 @@ private static void doServerStart(Vertx vertx, HttpBuildTimeConfig httpBuildTime
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, Handler<HttpServerRequest> managementRouter,
HttpConfiguration httpConfiguration, ManagementInterfaceConfiguration managementConfig,
LaunchMode launchMode,
Supplier<Integer> eventLoops, List<String> websocketSubProtocols, boolean auxiliaryApplication) throws IOException {
Supplier<Integer> eventLoops, List<String> 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,
Expand Down Expand Up @@ -842,18 +821,19 @@ public void handle(AsyncResult<Void> 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++;
Expand Down
Loading
Loading