From a4d19061e9b2ad0a91995cf2c8b122b2b674d5a8 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 19 Nov 2024 18:33:21 +0000 Subject: [PATCH] Ensure Forwarded and X-Forwarded values are the same --- ...llowForwardedAndXForwardedHeadersTest.java | 132 +++++++++++ ...dHeadersOverrideXForwardedHeadersTest.java | 37 +++ ...dHeadersOverrideForwardedHeadersTest.java} | 16 +- .../vertx/http/runtime/ForwardedParser.java | 218 +++++++++++++----- .../http/runtime/ForwardingProxyOptions.java | 13 +- .../vertx/http/runtime/ProxyConfig.java | 24 ++ 6 files changed, 377 insertions(+), 63 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedAndXForwardedHeadersTest.java create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedHeadersOverrideXForwardedHeadersTest.java rename extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/{AllowBothForwardedHeadersTest.java => AllowXForwardedHeadersOverrideForwardedHeadersTest.java} (66%) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedAndXForwardedHeadersTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedAndXForwardedHeadersTest.java new file mode 100644 index 0000000000000..585010d5ebebc --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedAndXForwardedHeadersTest.java @@ -0,0 +1,132 @@ +package io.quarkus.vertx.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class AllowForwardedAndXForwardedHeadersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ForwardedHandlerInitializer.class) + .addAsResource(new StringAsset("quarkus.http.proxy.proxy-address-forwarding=true\n" + + "quarkus.http.proxy.allow-forwarded=true\n" + + "quarkus.http.proxy.allow-x-forwarded=true\n" + + "quarkus.http.proxy.enable-forwarded-host=true\n" + + "quarkus.http.proxy.enable-forwarded-prefix=true\n" + + "quarkus.http.proxy.forwarded-host-header=X-Forwarded-Server"), + "application.properties")); + + @Test + public void testAllHeaderValuesMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Proto", "https") + .header("X-Forwarded-For", "backend2:5555") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .body(Matchers.equalTo("https|somehost2|backend2:5555|/path|https://somehost2/path")); + } + + @Test + public void tesProtoHeaderValuesMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Proto", "https") + .get("/path") + .then() + .body(Matchers.equalTo("https|somehost2|backend2:5555|/path|https://somehost2/path")); + } + + @Test + public void testForHeaderValuesMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-For", "backend2:5555") + .get("/path") + .then() + .body(Matchers.equalTo("https|somehost2|backend2:5555|/path|https://somehost2/path")); + } + + @Test + public void testHostHeaderValuesMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .body(Matchers.equalTo("https|somehost2|backend2:5555|/path|https://somehost2/path")); + } + + @Test + public void testProtoDoesNotMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Proto", "http") + .header("X-Forwarded-For", "backend2:5555") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .statusCode(400); + } + + @Test + public void testForHostDoesNotMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend:5555;host=somehost2") + .header("X-Forwarded-Proto", "http") + .header("X-Forwarded-For", "backend2:5555") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .statusCode(400); + } + + @Test + public void testForHostPortDoesNotMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:4444;host=somehost2") + .header("X-Forwarded-Proto", "http") + .header("X-Forwarded-For", "backend2:5555") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .statusCode(400); + } + + @Test + public void testHostDoesNotMatch() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:4444;host=somehost") + .header("X-Forwarded-Proto", "http") + .header("X-Forwarded-For", "backend2:5555") + .header("X-Forwarded-Server", "somehost2") + .get("/path") + .then() + .statusCode(400); + } +} \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedHeadersOverrideXForwardedHeadersTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedHeadersOverrideXForwardedHeadersTest.java new file mode 100644 index 0000000000000..323940aec5e32 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowForwardedHeadersOverrideXForwardedHeadersTest.java @@ -0,0 +1,37 @@ +package io.quarkus.vertx.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class AllowForwardedHeadersOverrideXForwardedHeadersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ForwardedHandlerInitializer.class) + .addAsResource(new StringAsset("quarkus.http.proxy.proxy-address-forwarding=true\n" + + "quarkus.http.proxy.allow-forwarded=true\n" + + "quarkus.http.proxy.allow-x-forwarded=true\n" + + "quarkus.http.proxy.strict-forwarded-control=false\n"), + "application.properties")); + + @Test + public void testXForwardedProtoOverridesForwardedProto() { + assertThat(RestAssured.get("/path").asString()).startsWith("http|"); + + RestAssured.given() + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Proto", "http") + .get("/path") + .then() + .body(Matchers.equalTo("https|somehost2|backend2:5555|/path|https://somehost2/path")); + } + +} \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowBothForwardedHeadersTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowXForwardedHeadersOverrideForwardedHeadersTest.java similarity index 66% rename from extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowBothForwardedHeadersTest.java rename to extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowXForwardedHeadersOverrideForwardedHeadersTest.java index 3427a18e3c667..9bcaf1d04715a 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowBothForwardedHeadersTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/AllowXForwardedHeadersOverrideForwardedHeadersTest.java @@ -10,7 +10,7 @@ import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; -public class AllowBothForwardedHeadersTest { +public class AllowXForwardedHeadersOverrideForwardedHeadersTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() @@ -19,22 +19,20 @@ public class AllowBothForwardedHeadersTest { .addAsResource(new StringAsset("quarkus.http.proxy.proxy-address-forwarding=true\n" + "quarkus.http.proxy.allow-forwarded=true\n" + "quarkus.http.proxy.allow-x-forwarded=true\n" + - "quarkus.http.proxy.enable-forwarded-host=true\n" + - "quarkus.http.proxy.enable-forwarded-prefix=true\n" + - "quarkus.http.proxy.forwarded-host-header=X-Forwarded-Server"), + "quarkus.http.proxy.forwarded-precedence=xforwarded\n" + + "quarkus.http.proxy.strict-forwarded-control=false\n"), "application.properties")); @Test - public void test() { + public void testXForwardedProtoOverridesForwardedProto() { assertThat(RestAssured.get("/path").asString()).startsWith("http|"); RestAssured.given() - .header("Forwarded", "proto=http;for=backend2:5555;host=somehost2") - .header("X-Forwarded-Proto", "https") - .header("X-Forwarded-For", "backend:4444") - .header("X-Forwarded-Server", "somehost") + .header("Forwarded", "proto=https;for=backend2:5555;host=somehost2") + .header("X-Forwarded-Proto", "http") .get("/path") .then() .body(Matchers.equalTo("http|somehost2|backend2:5555|/path|http://somehost2/path")); } + } \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java index 18068166e4f47..b5667a37a07fd 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java @@ -19,6 +19,10 @@ package io.quarkus.vertx.http.runtime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -131,60 +135,28 @@ private void calculate() { boolean isProxyAllowed = trustedProxyCheck.isProxyAllowed(); if (isProxyAllowed) { - String forwarded = delegate.getHeader(FORWARDED); - if (forwardingProxyOptions.allowForwarded && forwarded != null) { - Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwarded); - if (matcher.find()) { - scheme = (matcher.group(1).trim()); - port = -1; + if (forwardingProxyOptions.allowForwarded && forwardingProxyOptions.allowXForwarded) { + Forwarded forwardedHeaders = null; + Forwarded xForwardedHeaders = null; + if (ProxyConfig.ForwardedPrecedence.FORWARDED == forwardingProxyOptions.forwardedPrecedence) { + // Forwarded values may override X-Forwarded values if strict forwarded control is disabled + xForwardedHeaders = processXForwarded(); + forwardedHeaders = processForwarded(); + } else { + // X-Forwarded values may override Forwarded values if strict forwarded control is disabled + forwardedHeaders = processForwarded(); + xForwardedHeaders = processXForwarded(); } - - matcher = FORWARDED_HOST_PATTERN.matcher(forwarded); - if (matcher.find()) { - setHostAndPort(matcher.group(1).trim(), port); - } - - matcher = FORWARDED_FOR_PATTERN.matcher(forwarded); - if (matcher.find()) { - remoteAddress = parseFor(matcher.group(1).trim(), remoteAddress != null ? remoteAddress.port() : port); + if (forwardingProxyOptions.strictForwardedControl + && !xForwardedHeaders.modifiedPropertiesMatch(forwardedHeaders)) { + delegate.response().setStatusCode(400); + delegate.end(); + return; } + } else if (forwardingProxyOptions.allowForwarded) { + processForwarded(); } else if (forwardingProxyOptions.allowXForwarded) { - String protocolHeader = delegate.getHeader(X_FORWARDED_PROTO); - if (protocolHeader != null) { - scheme = getFirstElement(protocolHeader); - port = -1; - } - - String forwardedSsl = delegate.getHeader(X_FORWARDED_SSL); - boolean isForwardedSslOn = forwardedSsl != null && forwardedSsl.equalsIgnoreCase("on"); - if (isForwardedSslOn) { - scheme = HTTPS_SCHEME; - port = -1; - } - - if (forwardingProxyOptions.enableForwardedHost) { - String hostHeader = delegate.getHeader(forwardingProxyOptions.forwardedHostHeader); - if (hostHeader != null) { - setHostAndPort(getFirstElement(hostHeader), port); - } - } - - if (forwardingProxyOptions.enableForwardedPrefix) { - String prefixHeader = delegate.getHeader(forwardingProxyOptions.forwardedPrefixHeader); - if (prefixHeader != null) { - uri = appendPrefixToUri(prefixHeader, uri); - } - } - - String portHeader = delegate.getHeader(X_FORWARDED_PORT); - if (portHeader != null) { - port = parsePort(getFirstElement(portHeader), port); - } - - String forHeader = delegate.getHeader(X_FORWARDED_FOR); - if (forHeader != null) { - remoteAddress = parseFor(getFirstElement(forHeader), remoteAddress != null ? remoteAddress.port() : port); - } + processXForwarded(); } } @@ -210,7 +182,105 @@ private void calculate() { } absoluteURI = scheme + "://" + host + uri; - log.debug("Recalculated absoluteURI to " + absoluteURI); + log.debugf("Recalculated absoluteURI to %s", absoluteURI); + } + + private Forwarded processForwarded() { + Forwarded forwardedValues = new Forwarded(); + + String forwarded = delegate.getHeader(FORWARDED); + if (forwarded == null) { + return forwardedValues; + } + + Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwarded); + if (matcher.find()) { + scheme = matcher.group(1).trim(); + port = -1; + log.debugf("Using Forwarded 'proto' to set scheme to %s", scheme); + forwardedValues.setScheme(scheme); + forwardedValues.setPort(port); + } + + matcher = FORWARDED_HOST_PATTERN.matcher(forwarded); + if (matcher.find()) { + setHostAndPort(matcher.group(1).trim(), port); + log.debugf("Using Forwarded 'host' to set host to %s and port to %d", host, port); + forwardedValues.setHost(host); + forwardedValues.setPort(port); + } + + matcher = FORWARDED_FOR_PATTERN.matcher(forwarded); + if (matcher.find()) { + remoteAddress = parseFor(matcher.group(1).trim(), remoteAddress != null ? remoteAddress.port() : port); + forwardedValues.setRemoteHost(remoteAddress.host()); + forwardedValues.setRemotePort(remoteAddress.port()); + log.debugf("Using Forwarded 'for' to set for host to %s and for port to %d", remoteAddress.host(), + remoteAddress.port()); + } + + return forwardedValues; + } + + private Forwarded processXForwarded() { + Forwarded xForwardedValues = new Forwarded(); + + String protocolHeader = delegate.getHeader(X_FORWARDED_PROTO); + if (protocolHeader != null) { + scheme = getFirstElement(protocolHeader); + port = -1; + log.debugf("Using X-Forwarded-Proto to set scheme to %s", scheme); + xForwardedValues.setScheme(scheme); + xForwardedValues.setPort(port); + } + + String forwardedSsl = delegate.getHeader(X_FORWARDED_SSL); + boolean isForwardedSslOn = forwardedSsl != null && forwardedSsl.equalsIgnoreCase("on"); + if (isForwardedSslOn) { + scheme = HTTPS_SCHEME; + port = -1; + log.debugf("Using X-Forwarded-Ssl to set scheme to %s", scheme); + xForwardedValues.setScheme(scheme); + xForwardedValues.setPort(port); + } + + if (forwardingProxyOptions.enableForwardedHost) { + String hostHeader = delegate.getHeader(forwardingProxyOptions.forwardedHostHeader); + if (hostHeader != null) { + port = -1; + setHostAndPort(getFirstElement(hostHeader), port); + log.debugf("Using %s to set host to %s and port to %d", hostHeader, host, port); + xForwardedValues.setHost(host); + xForwardedValues.setPort(port); + } + } + + if (forwardingProxyOptions.enableForwardedPrefix) { + String prefixHeader = delegate.getHeader(forwardingProxyOptions.forwardedPrefixHeader); + if (prefixHeader != null) { + log.debugf("Using %s to prefix URI %s with prefix %s", forwardingProxyOptions.forwardedPrefixHeader, uri, + prefixHeader); + uri = appendPrefixToUri(prefixHeader, uri); + } + } + + String portHeader = delegate.getHeader(X_FORWARDED_PORT); + if (portHeader != null) { + port = parsePort(getFirstElement(portHeader), port); + log.debugf("Using X-Forwarded-Port to set port to %d", port); + xForwardedValues.setPort(port); + } + + String forHeader = delegate.getHeader(X_FORWARDED_FOR); + if (forHeader != null) { + remoteAddress = parseFor(getFirstElement(forHeader), remoteAddress != null ? remoteAddress.port() : port); + xForwardedValues.setRemoteHost(remoteAddress.host()); + xForwardedValues.setRemotePort(remoteAddress.port()); + log.debugf("Using X-Forwarded-For to set for host to %s and for port to %d", remoteAddress.host(), + remoteAddress.port()); + } + + return xForwardedValues; } private void setHostAndPort(String hostToParse, int defaultPort) { @@ -299,4 +369,48 @@ private String stripSlashes(String uri) { return result; } + static class Forwarded { + private static String SCHEME = "scheme"; + private static String HOST = "host"; + private static String PORT = "port"; + private static String REMOTE_HOST = "remote host"; + private static String REMOTE_PORT = "remote port"; + + private Map forwarded = new HashMap<>(); + + public void setScheme(String scheme) { + forwarded.put(SCHEME, scheme); + } + + public void setHost(String host) { + forwarded.put(HOST, host); + } + + public void setPort(Integer port) { + forwarded.put(PORT, port); + } + + public void setRemoteHost(String host) { + forwarded.put(REMOTE_HOST, host); + } + + public void setRemotePort(Integer port) { + forwarded.put(REMOTE_PORT, port); + } + + public boolean modifiedPropertiesMatch(Forwarded fw) { + Set keys = new HashSet<>(forwarded.keySet()); + keys.retainAll(fw.forwarded.keySet()); + + for (String key : keys) { + if (!forwarded.get(key).equals(fw.forwarded.get(key))) { + log.debugf("Forwarded and X-Forwarded %s values do not match", key); + return false; + } + } + + return true; + + } + } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardingProxyOptions.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardingProxyOptions.java index 23aafa044f1f7..0e9c30dae6894 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardingProxyOptions.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardingProxyOptions.java @@ -3,6 +3,7 @@ import java.util.List; import io.netty.util.AsciiString; +import io.quarkus.vertx.http.runtime.ProxyConfig.ForwardedPrecedence; import io.quarkus.vertx.http.runtime.TrustedProxyCheck.TrustedProxyCheckBuilder; import io.quarkus.vertx.http.runtime.TrustedProxyCheck.TrustedProxyCheckPart; @@ -14,6 +15,8 @@ public class ForwardingProxyOptions { final boolean enableForwardedPrefix; final AsciiString forwardedHostHeader; final AsciiString forwardedPrefixHeader; + final boolean strictForwardedControl; + final ForwardedPrecedence forwardedPrecedence; public final TrustedProxyCheckBuilder trustedProxyCheckBuilder; final boolean enableTrustedProxyHeader; @@ -24,6 +27,8 @@ public ForwardingProxyOptions(final boolean proxyAddressForwarding, boolean enableTrustedProxyHeader, AsciiString forwardedHostHeader, boolean enableForwardedPrefix, + boolean strictForwardedControl, + ForwardedPrecedence forwardedPrecedence, AsciiString forwardedPrefixHeader, TrustedProxyCheckBuilder trustedProxyCheckBuilder) { this.proxyAddressForwarding = proxyAddressForwarding; @@ -33,6 +38,8 @@ public ForwardingProxyOptions(final boolean proxyAddressForwarding, this.enableForwardedPrefix = enableForwardedPrefix; this.forwardedHostHeader = forwardedHostHeader; this.forwardedPrefixHeader = forwardedPrefixHeader; + this.strictForwardedControl = strictForwardedControl; + this.forwardedPrecedence = forwardedPrecedence; this.trustedProxyCheckBuilder = trustedProxyCheckBuilder; this.enableTrustedProxyHeader = enableTrustedProxyHeader; } @@ -44,6 +51,8 @@ public static ForwardingProxyOptions from(ProxyConfig proxy) { final boolean enableForwardedHost = proxy.enableForwardedHost; final boolean enableForwardedPrefix = proxy.enableForwardedPrefix; final boolean enableTrustedProxyHeader = proxy.enableTrustedProxyHeader; + final boolean strictForwardedControl = proxy.strictForwardedControl; + final ForwardedPrecedence forwardedPrecedence = proxy.forwardedPrecedence; final AsciiString forwardedPrefixHeader = AsciiString.cached(proxy.forwardedPrefixHeader); final AsciiString forwardedHostHeader = AsciiString.cached(proxy.forwardedHostHeader); @@ -53,7 +62,7 @@ public static ForwardingProxyOptions from(ProxyConfig proxy) { || parts.isEmpty() ? null : TrustedProxyCheckBuilder.builder(parts); return new ForwardingProxyOptions(proxyAddressForwarding, allowForwarded, allowXForwarded, enableForwardedHost, - enableTrustedProxyHeader, forwardedHostHeader, enableForwardedPrefix, forwardedPrefixHeader, - proxyCheckBuilder); + enableTrustedProxyHeader, forwardedHostHeader, enableForwardedPrefix, strictForwardedControl, + forwardedPrecedence, forwardedPrefixHeader, proxyCheckBuilder); } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ProxyConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ProxyConfig.java index 210fe6ddfb1ba..2711272f73320 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ProxyConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ProxyConfig.java @@ -52,6 +52,30 @@ public class ProxyConfig { @ConfigItem public Optional allowXForwarded; + /** + * When both Forwarded and X-Forwarded headers are enabled with {@link #allowForwarded} and {@link #allowXForwarded} + * respectively, enforce that the identical headers must have equal values. + */ + @ConfigItem(defaultValue = "true") + public boolean strictForwardedControl; + + public enum ForwardedPrecedence { + FORWARDED, + XFORWARDED + } + + /** + * When both Forwarded and X-Forwarded headers are enabled with {@link #allowForwarded} and {@link #allowXForwarded} + * respectively, and {@link #strictForwardedControl} enforcing that the identical headers must have equal values is + * disabled, + * choose if it is Forwarded or X-Forwarded matching header value that is preferred. + *

+ * For example, if Forwarded has a precedence over X-Forwarded, Forwarded scheme is `http` and X-Forwarded scheme is `https` + * then the final scheme value is `http`. If X-Forwarded has a preference, then the final scheme value is 'https'. + */ + @ConfigItem(defaultValue = "forwarded") + public ForwardedPrecedence forwardedPrecedence; + /** * Enable override the received request's host through a forwarded host header. */