Skip to content

Commit

Permalink
Merge pull request quarkusio#37324 from cescoffier/disable-http-doc
Browse files Browse the repository at this point in the history
Automatically disable the HTTP port when client certs are required
  • Loading branch information
sberyozkin authored Dec 6, 2023
2 parents b7887aa + 6f8706c commit 58834c2
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 65 deletions.
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 @@ 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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,15 @@ 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.
<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

0 comments on commit 58834c2

Please sign in to comment.