diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4ac983ab2e9c8..142e5759d51d3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -62,7 +62,7 @@ 2.1.0 1.0.13 3.0.1 - 3.6.0 + 3.7.2 4.10.1 2.3.1 2.1.2 @@ -120,7 +120,7 @@ 1.0.1.Final 2.2.2.Final 3.5.0.Final - 4.4.5 + 4.4.6 4.5.14 4.4.16 4.1.5 diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index 873436fd8942e..d3e5399bf6cbe 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -16,7 +16,7 @@ include::_attributes.adoc[] :httpspec: https://tools.ietf.org/html/rfc7231 :jsonpapi: https://javadoc.io/doc/jakarta.json/jakarta.json-api/2.1.2/jakarta.json :injectapi: https://javadoc.io/static/jakarta.inject/jakarta.inject-api/2.0.1/jakarta.inject -:vertxapi: https://javadoc.io/static/io.vertx/vertx-core/4.4.5 +:vertxapi: https://javadoc.io/static/io.vertx/vertx-core/4.4.6 :resteasy-reactive-api: https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive/{quarkus-version} :resteasy-reactive-common-api: https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-common/{quarkus-version} diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/MoneyTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/MoneyTest.java new file mode 100644 index 0000000000000..b479c59b3cc2a --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/MoneyTest.java @@ -0,0 +1,26 @@ +package io.quarkus.reactive.pg.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; + +import io.vertx.pgclient.data.Money; + +/** + * Reproduce PG Reactive Client: Cannot create Money value in Range + * (-1.00, 0.00). + */ +public class MoneyTest { + + @Test + void testMoney() { + Money money = new Money(new BigDecimal("-1.11")); + assertEquals(BigDecimal.valueOf(-1.11), money.bigDecimalValue()); + + money = new Money(new BigDecimal("-0.11")); + assertEquals(BigDecimal.valueOf(-0.11), money.bigDecimalValue()); + } + +} diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/EmptyHostTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/EmptyHostTest.java new file mode 100644 index 0000000000000..bcece8cde9f19 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/EmptyHostTest.java @@ -0,0 +1,46 @@ +package io.quarkus.vertx.http; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.vertx.ext.web.Router; + +/** + * Reproduce NullPointerException for request with empty Host + * header. + */ +public class EmptyHostTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(BeanRegisteringRouteUsingObserves.class)); + + @Test + public void testWithEmptyHost() { + assertEquals(RestAssured + .given() + .header("Host", "") + .get("/hello") + .asString(), "Hello World! "); + + } + + @ApplicationScoped + static class BeanRegisteringRouteUsingObserves { + + public void register(@Observes Router router) { + + router.route("/hello").handler(ctx -> ctx.response().end("Hello World! " + ctx.request().host())); + } + + } + +} diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java new file mode 100644 index 0000000000000..5f4091a2b7097 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java @@ -0,0 +1,105 @@ +package io.quarkus.vertx.http.http2; + +import static io.vertx.core.http.HttpMethod.GET; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.vertx.core.runtime.VertxCoreRecorder; +import io.vertx.core.http.HttpClient; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpVersion; +import io.vertx.core.net.JdkSSLEngineOptions; +import io.vertx.ext.web.Router; + +/** + * Configuration of the RST flood protection (CVE-2023-44487) + */ +public class Http2RSTFloodProtectionConfigTest { + + @TestHTTPResource(value = "/ping", ssl = true) + URL sslUrl; + + @TestHTTPResource(value = "/ping") + URL url; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(MyBean.class) + .addAsResource(new File("src/test/resources/conf/ssl-jks-rst-flood-protection.conf"), + "application.properties") + .addAsResource(new File("src/test/resources/conf/server-keystore.jks"), "server-keystore.jks")); + + @Test + void testRstFloodProtectionWithTlsEnabled() throws Exception { + Assumptions.assumeTrue(JdkSSLEngineOptions.isAlpnAvailable()); //don't run on JDK8 + HttpClientOptions options = new HttpClientOptions() + .setUseAlpn(true) + .setProtocolVersion(HttpVersion.HTTP_2) + .setSsl(true) + .setTrustAll(true); + + var client = VertxCoreRecorder.getVertx().get().createHttpClient(options); + int port = sslUrl.getPort(); + run(client, port, false); + } + + @Test + public void testRstFloodProtection() throws InterruptedException { + HttpClientOptions options = new HttpClientOptions() + .setProtocolVersion(HttpVersion.HTTP_2) + .setHttp2ClearTextUpgrade(true); + var client = VertxCoreRecorder.getVertx().get().createHttpClient(options); + run(client, url.getPort(), true); + } + + void run(HttpClient client, int port, boolean plain) throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + client.connectionHandler(conn -> conn.goAwayHandler(ga -> { + Assertions.assertEquals(11, ga.getErrorCode()); + latch.countDown(); + })); + + if (plain) { + // Emit a first request to establish a connection. + // It's HTTP/1 so, does not count in the number of requests. + client.request(GET, port, "localhost", "/ping") + .compose(HttpClientRequest::send); + } + + for (int i = 0; i < 20; i++) { + client.request(GET, port, "localhost", "/ping") + .onSuccess(req -> req.end().onComplete(v -> req.reset())); + } + + if (!latch.await(10, TimeUnit.SECONDS)) { + fail("RST flood protection failed"); + } + } + + @ApplicationScoped + public static class MyBean { + + public void register(@Observes Router router) { + router.get("/ping").handler(rc -> { + // Do nothing. + }); + } + + } +} diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/ssl-jks-rst-flood-protection.conf b/extensions/vertx-http/deployment/src/test/resources/conf/ssl-jks-rst-flood-protection.conf new file mode 100644 index 0000000000000..09b82c863ebe4 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/resources/conf/ssl-jks-rst-flood-protection.conf @@ -0,0 +1,6 @@ + +quarkus.http.ssl.certificate.key-store-file=server-keystore.jks +quarkus.http.ssl.certificate.key-store-password=secret + + quarkus.http.limits.rst-flood-max-rst-frame-per-window=10 + quarkus.http.limits.rst-flood-window-duration=10s \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerLimitsConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerLimitsConfig.java index 1d8e91e323eed..1ef9a2b47a6fe 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerLimitsConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerLimitsConfig.java @@ -1,5 +1,6 @@ package io.quarkus.vertx.http.runtime; +import java.time.Duration; import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; @@ -86,4 +87,20 @@ public class ServerLimitsConfig { @ConfigItem public OptionalLong maxHeaderListSize; + /** + * Set the max number of RST frame allowed per time window, this is used to prevent + * HTTP/2 RST frame flood DDOS + * attacks. The default value is {@code 200}, setting zero or a negative value, disables flood protection. + */ + @ConfigItem + public OptionalInt rstFloodMaxRstFramePerWindow; + + /** + * Set the duration of the time window when checking the max number of RST frames, this is used to prevent + * HTTP/2 RST frame flood DDOS + * attacks.. The default value is {@code 30 s}, setting zero or a negative value, disables flood protection. + */ + @ConfigItem + public Optional rstFloodWindowDuration; + } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java index 20e9fa1eb6193..36239a6ceaa1f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java @@ -275,6 +275,18 @@ public static void applyCommonOptions(HttpServerOptions httpServerOptions, settings.setMaxHeaderListSize(httpConfiguration.limits.maxHeaderListSize.getAsLong()); } httpServerOptions.setInitialSettings(settings); + + // RST attack protection - https://github.com/netty/netty/security/advisories/GHSA-xpw8-rcwv-8f8p + if (httpConfiguration.limits.rstFloodMaxRstFramePerWindow.isPresent()) { + httpServerOptions + .setHttp2RstFloodMaxRstFramePerWindow(httpConfiguration.limits.rstFloodMaxRstFramePerWindow.getAsInt()); + } + if (httpConfiguration.limits.rstFloodWindowDuration.isPresent()) { + httpServerOptions.setHttp2RstFloodWindowDuration( + (int) httpConfiguration.limits.rstFloodWindowDuration.get().toSeconds()); + httpServerOptions.setHttp2RstFloodWindowDurationTimeUnit(TimeUnit.SECONDS); + } + } httpServerOptions.setUseProxyProtocol(httpConfiguration.proxy.useProxyProtocol); diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index bd02b6ce15ad0..4920d8e9a3607 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -63,7 +63,7 @@ 3.1.2 2.5.1 2.1.2 - 4.4.5 + 4.4.6 5.3.2 1.0.0.Final 2.15.2