Skip to content

Commit 867a4ce

Browse files
author
matthaios.stavrou
committed
Expose Undertow listener configuration options (disallowed-methods, record-request-start-time)
1 parent 7d3b3eb commit 867a4ce

File tree

6 files changed

+138
-3
lines changed

6 files changed

+138
-3
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 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.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.disallowed-methods=TRACE,TRACK
657+
----
658+
659+
| `quarkus.undertow.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.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: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import java.util.ArrayList;
1010
import java.util.Collections;
1111
import java.util.EventListener;
12+
import java.util.HashSet;
1213
import java.util.List;
14+
import java.util.Locale;
1315
import java.util.Optional;
1416
import java.util.Set;
1517
import java.util.concurrent.CopyOnWriteArrayList;
@@ -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

@@ -450,6 +476,7 @@ public void run() {
450476
}
451477
});
452478
}
479+
453480
}
454481
};
455482
}
@@ -822,9 +849,8 @@ public void setLength(final int length) {
822849
}
823850

824851
/**
825-
* Encode the bytes into a String with a slightly modified Base64-algorithm
826-
* This code was written by Kevin Kelley <kelley@ruralnet.net>
827-
* and adapted by Thomas Peuss <jboss@peuss.de>
852+
* Encode the bytes into a String with a slightly modified Base64-algorithm This code was written by Kevin Kelley
853+
* <kelley@ruralnet.net> and adapted by Thomas Peuss <jboss@peuss.de>
828854
*
829855
* @param data The bytes you want to encode
830856
* @return the encoded String

0 commit comments

Comments
 (0)