Skip to content

Commit 16cc93c

Browse files
author
matthaios.stavrou
committed
Expose Undertow listener configuration options (disallowed-methods, record-request-start-time)
1 parent 809be8f commit 16cc93c

File tree

6 files changed

+135
-0
lines changed

6 files changed

+135
-0
lines changed

docs/src/main/asciidoc/http-reference.adoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,3 +636,36 @@ Route order constants defined in `io.quarkus.vertx.http.runtime.RouteConstants`
636636
| `10000` | `ROUTE_ORDER_DEFAULT` | Default route order (i.e. Static Resources, Servlet).
637637
| `20000` | `ROUTE_ORDER_AFTER_DEFAULT` | Route without priority over the default route (add an offset from this value)
638638
|===
639+
640+
=== Undertow listener configuration
641+
642+
The following configuration properties are available when using the Undertow servlet extension.
643+
644+
[cols="3,1,1,5",options="header"]
645+
|===
646+
|Configuration property |Type |Default |Description
647+
648+
|`quarkus.undertow.listener.disallowed-methods`
649+
|list of string
650+
|`<empty>`
651+
|A list of HTTP methods that should be rejected with `405 Method Not Allowed`.
652+
Useful for disabling insecure methods such as `TRACE` or `TRACK`.
653+
654+
Example:
655+
----
656+
quarkus.undertow.listener.disallowed-methods=TRACE,TRACK
657+
----
658+
659+
|`quarkus.undertow.listener.record-request-start-time`
660+
|boolean
661+
|false
662+
|If enabled, Undertow records the request start timestamp.
663+
Useful for timing, logging and request tracing.
664+
665+
|`quarkus.undertow.listener.max-parameters`
666+
|int
667+
|1000
668+
|Maximum number of allowed HTTP parameters.
669+
Provides protection against parameter-based DoS attacks.
670+
671+
|===
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.quarkus.undertow.test;
2+
3+
import static io.restassured.RestAssured.given;
4+
import static org.hamcrest.Matchers.equalTo;
5+
6+
import java.net.URI;
7+
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.test.QuarkusUnitTest;
12+
import io.quarkus.test.common.http.TestHTTPResource;
13+
import io.restassured.http.Method;
14+
15+
public class DisallowedMethodsTest {
16+
17+
@RegisterExtension
18+
static final QuarkusUnitTest test = new QuarkusUnitTest()
19+
.withApplicationRoot(jar -> jar
20+
.addClass(DisallowedMethodsTestServlet.class))
21+
.withConfigurationResource("application-disallowed-methods.properties");
22+
23+
@TestHTTPResource("/disallowed")
24+
URI testUri;
25+
26+
@Test
27+
public void traceMethodShouldBeBlockedWith405() {
28+
given()
29+
.when()
30+
.request(Method.TRACE, testUri)
31+
.then()
32+
.statusCode(405);
33+
}
34+
35+
@Test
36+
public void getMethodShouldStillWork() {
37+
given()
38+
.when()
39+
.get(testUri)
40+
.then()
41+
.statusCode(200)
42+
.body(equalTo("ok"));
43+
}
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.quarkus.undertow.test;
2+
3+
import java.io.IOException;
4+
5+
import jakarta.servlet.annotation.WebServlet;
6+
import jakarta.servlet.http.HttpServlet;
7+
import jakarta.servlet.http.HttpServletRequest;
8+
import jakarta.servlet.http.HttpServletResponse;
9+
10+
@WebServlet(urlPatterns = "/disallowed")
11+
public class DisallowedMethodsTestServlet extends HttpServlet {
12+
13+
@Override
14+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
15+
resp.getWriter().write("ok");
16+
}
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
quarkus.servlet.disallowed-methods=TRACE

extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/ServletRuntimeConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.quarkus.undertow.runtime;
22

33
import java.util.Optional;
4+
import java.util.Set;
45

56
import io.quarkus.runtime.annotations.ConfigPhase;
67
import io.quarkus.runtime.annotations.ConfigRoot;
@@ -34,4 +35,17 @@ public interface ServletRuntimeConfig {
3435
*/
3536
@WithDefault("1000")
3637
int maxParameters();
38+
39+
/**
40+
* A list of HTTP methods that are disallowed for servlet requests.
41+
* Requests using these methods will be rejected before being processed.
42+
*/
43+
Optional<Set<String>> disallowedMethods();
44+
45+
/**
46+
* Whether to record the request start time for each HTTP request.
47+
* Useful for access logs and response time metrics.
48+
*/
49+
@WithDefault("false")
50+
boolean recordRequestStartTime();
3751
}

extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import java.util.Collections;
1111
import java.util.EventListener;
1212
import java.util.List;
13+
import java.util.Locale;
1314
import java.util.Optional;
1415
import java.util.Set;
16+
import java.util.HashSet;
1517
import java.util.concurrent.CopyOnWriteArrayList;
1618
import java.util.concurrent.ExecutorService;
1719
import java.util.function.Supplier;
@@ -394,19 +396,43 @@ public void run() {
394396

395397
UndertowOptionMap.Builder undertowOptions = UndertowOptionMap.builder();
396398
undertowOptions.set(UndertowOptions.MAX_PARAMETERS, servletRuntimeConfig.getValue().maxParameters());
399+
undertowOptions.set(UndertowOptions.RECORD_REQUEST_START_TIME,
400+
servletRuntimeConfig.getValue().recordRequestStartTime());
397401
UndertowOptionMap undertowOptionMap = undertowOptions.getMap();
398402

399403
Set<String> compressMediaTypes = httpBuildTimeConfig.enableCompression()
400404
? Set.copyOf(httpBuildTimeConfig.compressMediaTypes().get())
401405
: Collections.emptySet();
402406

407+
Set<String> disallowedMethods;
408+
Optional<Set<String>> configured = servletRuntimeConfig.getValue().disallowedMethods();
409+
if (configured.isPresent()) {
410+
Set<String> normalized = new HashSet<>();
411+
for (String method : configured.get()) {
412+
if (method != null && !method.isBlank()) {
413+
normalized.add(method.trim().toUpperCase(Locale.ROOT));
414+
}
415+
}
416+
disallowedMethods = normalized;
417+
} else {
418+
disallowedMethods = Collections.emptySet();
419+
}
420+
403421
return new Handler<RoutingContext>() {
404422
@Override
405423
public void handle(RoutingContext event) {
406424
if (!event.request().isEnded()) {
407425
event.request().pause();
408426
}
409427

428+
boolean disallowedMethod = !disallowedMethods.isEmpty()
429+
&& disallowedMethods.contains(event.request().method().name());
430+
431+
if (disallowedMethod) {
432+
event.response().setStatusCode(StatusCodes.METHOD_NOT_ALLOWED).end("Method Not Allowed");
433+
event.response().end();
434+
return;
435+
}
410436
//we handle auth failure directly
411437
event.remove(QuarkusHttpUser.AUTH_FAILURE_HANDLER);
412438

0 commit comments

Comments
 (0)