Skip to content

Commit

Permalink
This commit adds management interface support.
Browse files Browse the repository at this point in the history
It allows exposing selected routes (management routes) to a different HTTP server.
It avoids exposing these management routes on the main HTTP server, which could lead to leaks and undesired access to these endpoints.

Enabling/Disabling the management interface is a build-time property.
However, the interface, port, and SSL... are runtime values.

The management interface is not intended to be used using native transport (as high concurrency is rarely a need for these endpoints).
Also, the access log and same site cookie are not supported yet.

The management interface does not expose plain and secured endpoints.
It's either using HTTP or HTTPS.

At the moment are considered management routes:

* health routes (but not the health-ui)
* prometheus routes
* metrics routes
  • Loading branch information
cescoffier committed Feb 6, 2023
1 parent 3851228 commit 2578b4f
Show file tree
Hide file tree
Showing 52 changed files with 2,586 additions and 516 deletions.
4 changes: 2 additions & 2 deletions .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
},
{
"category": "HTTP",
"timeout": 90,
"test-modules": "elytron-resteasy, resteasy-jackson, elytron-resteasy-reactive, resteasy-mutiny, resteasy-reactive-kotlin/standard, vertx, vertx-http, vertx-web, vertx-web-jackson, vertx-graphql, virtual-http, rest-client, rest-client-reactive, rest-client-reactive-stork, rest-client-reactive-multipart",
"timeout": 95,
"test-modules": "elytron-resteasy, resteasy-jackson, elytron-resteasy-reactive, resteasy-mutiny, resteasy-reactive-kotlin/standard, vertx, vertx-http, vertx-web, vertx-web-jackson, vertx-graphql, virtual-http, rest-client, rest-client-reactive, rest-client-reactive-stork, rest-client-reactive-multipart, management-interface",
"os-name": "ubuntu-latest"
},
{
Expand Down
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 @@ -261,7 +261,7 @@ include::{generated-dir}/config/quarkus-vertx-http-config-group-filter-config.ad
== Support 100-Continue in vert.x

In order to support `100-continue`, the `quarkus.http.handle-100-continue-automatically` option needs to be enabled explicitly
For additional information check https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1[100-continue= and the related
For additional information check https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1[100-continue] and the related
https://vertx.io/docs/apidocs/io/vertx/core/http/HttpServerOptions.html#DEFAULT_HANDLE_100_CONTINE_AUTOMATICALLY[Vert.x documentation].

[source,properties]
Expand Down
163 changes: 163 additions & 0 deletions docs/src/main/asciidoc/management-interface-reference.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Management interface reference
include::_attributes.adoc[]
:categories: observability
:summary: Learn how you can use a separated management interface
:numbered:
:sectnums:
:sectnumlevels: 4

:numbered:
:sectnums:
:sectnumlevels: 4


By default, Quarkus exposes the _management_ endpoints under `/q` on the main HTTP server.
The same HTTP server provides the application endpoints and the management endpoints.

This document presents how you can use a separate HTTP server (bound to a different network interface and port) for the management endpoints.
It avoids exposing these endpoints on the main server and, therefore, prevents undesired accesses.

== Enabling the management interface

To enable the management interface, use the following **build-time** property:

[source, properties]
----
quarkus.management.enabled=true
----

Management endpoints will be exposed on: `http://0.0.0.0:8999/q`.
For example, `http://0.0.0.0:8998/q/health/ready` for the readiness probe.

== Configure the host, port and scheme

By default, the management interface is exposed on the interface: `0.0.0.0` (all interfaces) and on the port `8999` (`8998` in test mode).
It does not use TLS (`https`) by default.

You can configure the host, ports, and TLS certificates using the following properties:

* `quarkus.management.host` - the interface / host
* `quarkus.management.port` - the port
* `quarkus.management.test-port` - the port to use in test mode
* `quarkus.management.ssl` - the TLS configuration, xref:http-reference#ssl[same as for the main HTTP server].

Here is an example for properties exposing the management interface on _https://localhost:9000_:

[source, properties]
----
quarkus.management.enabled=true
quarkus.management.host=localhost
quarkus.management.port=9000
quarkus.management.ssl.certificate.key-store-file=server-keystore.jks
quarkus.management.ssl.certificate.key-store-password=secret
----

IMPORTANT: Unlike the main HTTP server, the management interface does not handle _http_ and _https_ at the same time.
If _https_ is configured, plain HTTP requests will be rejected.

== Configure the root path

By default, management endpoints are exposed under `/q`.
This _root path_ can be configured using the `quarkus.management.root-path` property.
For example, if you want to expose the management endpoints under `/management` use:

[source, properties]
----
quarkus.management.enabled=true
quarkus.management.root-path=/management
----

The mounting rules of the management endpoints slightly differ from the ones used when using the main HTTP server:

* Management endpoints configured using a _relative_ path (not starting with `/`) will be served from the configured root path.
For example, if the endpoint path is `health` and the root path is `management`, the resulting path is `/management/health`
* Management endpoints configured using an _absolute_ path (starting with `/`) will be served from the root.
For example, if the endpoint path is `/health`, the resulting path is `/health`, regardless of the root path
* The management interface does not use the HTTP root path from the main HTTP server.

== Management endpoints

SmallRye Health Checks, SmallRye Metrics and Prometheus are declared as management endpoints.

NOTE: if you do not enable the management interface, these endpoints will be served using the main HTTP server (under `/q` by default).

To declare a management endpoint, an extension must produce a _non application_ route and call the `management()` method:

[source, java]
----
@BuildStep
void createManagementRoute(BuildProducer<RouteBuildItem> routes,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
MyRecorder recorder) {
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management() // Must be called BEFORE the routeFunction method
.routeFunction("my-path", recorder.route())
.handler(recorder.getHandler())
.blockingRoute()
.build());
//...
}
----

If the management interface is enabled, the endpoint will be exposed on: `http://0.0.0.0:8999/q/my-path`.
Otherwise, it will be exposed on: `http://localhost:8080/q/my-path`.

IMPORTANT: Management endpoints can only be declared by extensions and not from the application code.

== Management Interface Configuration

include::{generated-dir}/config/quarkus-management-management-management-interface-build-time-config.adoc[leveloffset=+1, opts=optional]

include::{generated-dir}/config/quarkus-management-management-management-interface-configuration.adoc[leveloffset=+1, opts=optional]


[[reverse-proxy]]
== Running behind a reverse proxy

Quarkus could be accessed through proxies that additionally generate headers (e.g. `X-Forwarded-Host`) to keep
information from the client-facing side of the proxy servers that is altered or lost when they are involved.
In those scenarios, Quarkus can be configured to automatically update information like protocol, host, port and URI
reflecting the values in these headers.

IMPORTANT: Activating this feature leaves the server exposed to several security issues (i.e. information spoofing).
Consider activate it only when running behind a reverse proxy.

To set up this feature for the management interface, include the following lines in `src/main/resources/application.properties`:
[source,properties]
----
quarkus.management.proxy.proxy-address-forwarding=true
----

To consider only de-facto standard header (`Forwarded` header), please include the following lines in `src/main/resources/application.properties`:
[source,properties]
----
quarkus.management.proxy.allow-forwarded=true
----

To consider only non-standard headers, please include the following lines instead in `src/main/resources/application.properties`:

[source,properties]
----
quarkus.management.proxy.proxy-address-forwarding=true
quarkus.management.proxy.allow-x-forwarded=true
quarkus.management.proxy.enable-forwarded-host=true
quarkus.management.proxy.enable-forwarded-prefix=true
----

Both configurations related to standard and non-standard headers can be combined, although the standard headers configuration will have precedence. However, combining them has security implications as clients can forge requests with a forwarded header that is not overwritten by the proxy.
Therefore, proxies should strip unexpected `X-Forwarded` or `X-Forwarded-*` headers from the client.

Supported forwarding address headers are:

* `Forwarded`
* `X-Forwarded-Proto`
* `X-Forwarded-Host`
* `X-Forwarded-Port`
* `X-Forwarded-Ssl`
* `X-Forwarded-Prefix`
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ public void postBuild(ContainerImageInfoBuildItem image, List<ContainerImageBuil
ExecUtil.exec("kind", "load", "docker-image", image.getImage());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic
readinessPath,
roles, roleBindings, customProjectRoot);
}
}
}
4 changes: 4 additions & 0 deletions extensions/kubernetes/vanilla/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client-internal-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,4 @@ private static int getStablePortNumberInRange(String input, int min, int max) {
throw new RuntimeException("Unable to generate stable port number from input string: '" + input + "'", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,4 @@ private static List<DecoratorBuildItem> createVolumeDecorators(Optional<Project>
});
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -740,4 +740,4 @@ private static Map<String, Integer> verifyPorts(List<KubernetesPortBuildItem> ku
}
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,4 @@ void externalizeInitTasks(
decorators);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,4 @@ void externalizeInitTasks(
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ void createPrometheusRoute(BuildProducer<RouteBuildItem> routes,

// Exact match for resources matched to the root path
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management()
.routeFunction(pConfig.path, recorder.route())
.routeConfigKey("quarkus.micrometer.export.prometheus.path")
.handler(recorder.getHandler())
Expand All @@ -104,17 +105,20 @@ void createPrometheusRoute(BuildProducer<RouteBuildItem> routes,

// Match paths that begin with the deployment path
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management()
.routeFunction(pConfig.path + (pConfig.path.endsWith("/") ? "*" : "/*"), recorder.route())
.handler(recorder.getHandler())
.blockingRoute()
.build());

// Fallback paths (for non text/plain requests)
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management()
.routeFunction(pConfig.path, recorder.fallbackRoute())
.handler(recorder.getFallbackHandler())
.build());
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
.management()
.routeFunction(pConfig.path + (pConfig.path.endsWith("/") ? "*" : "/*"), recorder.fallbackRoute())
.handler(recorder.getFallbackHandler())
.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.micrometer.deployment.export;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class PrometheusEnabledOnManagementInterfaceTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setFlatClassPath(true)
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.export.prometheus.enabled", "true")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.overrideConfigKey("quarkus.redis.devservices.enabled", "false")
.overrideConfigKey("quarkus.management.enabled", "true")
.withEmptyApplication();

@Test
public void metricsEndpoint() {
RestAssured.given()
.accept("application/json")
.get("http://0.0.0.0:8998/q/metrics")
.then()
.log().all()
.statusCode(406);

RestAssured.given()
.get("http://0.0.0.0:8998/q/metrics")
.then()
.statusCode(200);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
Expand All @@ -19,7 +20,8 @@ void setup(@Observes Router router) {
HttpConfiguration httpConfiguration = new HttpConfiguration();
ConfigInstantiator.handleObject(httpConfiguration);
Handler<RoutingContext> bodyHandler = new VertxHttpRecorder(new HttpBuildTimeConfig(),
new RuntimeValue<>(httpConfiguration))
new ManagementInterfaceBuildTimeConfig(),
new RuntimeValue<>(httpConfiguration), null)
.createBodyHandler();
router.route().order(Integer.MIN_VALUE + 1).handler(new Handler<RoutingContext>() {
@Override
Expand Down
Loading

0 comments on commit 2578b4f

Please sign in to comment.