Skip to content

Commit

Permalink
Ensure Forwarded and X-Forwarded values are the same
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Nov 22, 2024
1 parent cbd735f commit 6e9a414
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,108 @@ public class AllowBothForwardedHeadersTest {
"application.properties"));

@Test
public void test() {
public void testAllHeaderValuesMatch() {
assertThat(RestAssured.get("/path").asString()).startsWith("http|");

RestAssured.given()
.header("Forwarded", "proto=http;for=backend2:5555;host=somehost2")
.header("Forwarded", "proto=https;for=backend2:5555;host=somehost2")
.header("X-Forwarded-Proto", "https")
.header("X-Forwarded-For", "backend:4444")
.header("X-Forwarded-Server", "somehost")
.header("X-Forwarded-For", "backend2:5555")
.header("X-Forwarded-Server", "somehost2")
.get("/path")
.then()
.body(Matchers.equalTo("http|somehost2|backend2:5555|/path|http://somehost2/path"));
.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);
}
}
Original file line number Diff line number Diff line change
@@ -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("http|somehost2|backend2:5555|/path|http://somehost2/path"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -131,60 +135,23 @@ 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;
}

matcher = FORWARDED_HOST_PATTERN.matcher(forwarded);
if (matcher.find()) {
setHostAndPort(matcher.group(1).trim(), port);
}
Forwarded forwardedHeaders = null;

matcher = FORWARDED_FOR_PATTERN.matcher(forwarded);
if (matcher.find()) {
remoteAddress = parseFor(matcher.group(1).trim(), remoteAddress != null ? remoteAddress.port() : port);
}
} else if (forwardingProxyOptions.allowXForwarded) {
String protocolHeader = delegate.getHeader(X_FORWARDED_PROTO);
if (protocolHeader != null) {
scheme = getFirstElement(protocolHeader);
port = -1;
}
if (forwardingProxyOptions.allowForwarded) {
forwardedHeaders = processForwarded();
}

String forwardedSsl = delegate.getHeader(X_FORWARDED_SSL);
boolean isForwardedSslOn = forwardedSsl != null && forwardedSsl.equalsIgnoreCase("on");
if (isForwardedSslOn) {
scheme = HTTPS_SCHEME;
port = -1;
}
if (forwardingProxyOptions.allowXForwarded) {

if (forwardingProxyOptions.enableForwardedHost) {
String hostHeader = delegate.getHeader(forwardingProxyOptions.forwardedHostHeader);
if (hostHeader != null) {
setHostAndPort(getFirstElement(hostHeader), port);
}
}
Forwarded xForwardedHeaders = processXForwarded();

if (forwardingProxyOptions.enableForwardedPrefix) {
String prefixHeader = delegate.getHeader(forwardingProxyOptions.forwardedPrefixHeader);
if (prefixHeader != null) {
uri = appendPrefixToUri(prefixHeader, uri);
}
if (forwardedHeaders != null && forwardingProxyOptions.strictForwardedControl
&& !xForwardedHeaders.modifiedAddressPropertiesMatch(forwardedHeaders)) {
delegate.response().setStatusCode(400);
delegate.end();
return;
}

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);
}
}
}

Expand Down Expand Up @@ -213,6 +180,90 @@ private void calculate() {
log.debug("Recalculated absoluteURI to " + absoluteURI);
}

private Forwarded processForwarded() {
String forwarded = delegate.getHeader(FORWARDED);
if (forwarded == null) {
return null;
}
Forwarded forwardedValues = new Forwarded();

Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwarded);
if (matcher.find()) {
scheme = matcher.group(1).trim();
port = -1;
forwardedValues.setScheme(scheme);
forwardedValues.setPort(port);
}

matcher = FORWARDED_HOST_PATTERN.matcher(forwarded);
if (matcher.find()) {
setHostAndPort(matcher.group(1).trim(), 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());
}

return forwardedValues;
}

private Forwarded processXForwarded() {
Forwarded xForwardedValues = new Forwarded();

String protocolHeader = delegate.getHeader(X_FORWARDED_PROTO);
if (protocolHeader != null) {
scheme = getFirstElement(protocolHeader);
port = -1;
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;
xForwardedValues.setScheme(scheme);
xForwardedValues.setPort(port);
}

if (forwardingProxyOptions.enableForwardedHost) {
String hostHeader = delegate.getHeader(forwardingProxyOptions.forwardedHostHeader);
if (hostHeader != null) {
setHostAndPort(getFirstElement(hostHeader), port);
xForwardedValues.setHost(host);
xForwardedValues.setPort(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);
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());
}

return xForwardedValues;
}

private void setHostAndPort(String hostToParse, int defaultPort) {
if (hostToParse == null) {
hostToParse = "";
Expand Down Expand Up @@ -299,4 +350,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<String, Object> 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 modifiedAddressPropertiesMatch(Forwarded fw) {
Set<String> 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;

}
}
}
Loading

0 comments on commit 6e9a414

Please sign in to comment.