Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add x-forwarded-prefix header #382

Merged
merged 9 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@

package org.springframework.cloud.gateway.filter.headers;

import java.net.URI;
import java.util.LinkedHashSet;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

@ConfigurationProperties("spring.cloud.gateway.x-forwarded")
public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/** default http port */
Expand All @@ -52,6 +56,10 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/** X-Forwarded-Proto Header */
public static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";

/** X-Forwarded-Prefix Header */
public static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix";


/** The order of the XForwardedHeadersFilter. */
private int order = 0;

Expand All @@ -70,6 +78,9 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/** If X-Forwarded-Proto is enabled. */
private boolean protoEnabled = true;

/** If X-Forwarded-Prefix is enabled. */
private boolean prefixEnabled = true;

/** If appending X-Forwarded-For as a list is enabled. */
private boolean forAppend = true;

Expand All @@ -82,6 +93,9 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/** If appending X-Forwarded-Proto as a list is enabled. */
private boolean protoAppend = true;

/** If appending X-Forwarded-Prefix as a list is enabled. */
private boolean prefixAppend = true;

@Override
public int getOrder() {
return this.order;
Expand Down Expand Up @@ -131,6 +145,14 @@ public void setProtoEnabled(boolean protoEnabled) {
this.protoEnabled = protoEnabled;
}

public boolean isPrefixEnabled() {
return prefixEnabled;
}

public void setPrefixEnabled(boolean prefixEnabled) {
this.prefixEnabled = prefixEnabled;
}

public boolean isForAppend() {
return forAppend;
}
Expand Down Expand Up @@ -163,8 +185,18 @@ public void setProtoAppend(boolean protoAppend) {
this.protoAppend = protoAppend;
}

public void setPrefixAppend(boolean prefixAppend) {
this.prefixAppend = prefixAppend;
}

public boolean isPrefixAppend() {
return prefixAppend;
}

@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {


ServerHttpRequest request = exchange.getRequest();
HttpHeaders original = input;
HttpHeaders updated = new HttpHeaders();
Expand All @@ -187,6 +219,37 @@ public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
write(updated, X_FORWARDED_PROTO_HEADER, proto, isProtoAppend());
}

if(isPrefixEnabled()) {
//if the path of the url that the gw is routing to is a subset (and ending part) of the url that it is routing from then the difference is the prefix
//e.g. if request original.com/prefix/get/ is routed to routedservice:8090/get then /prefix is the prefix - see XForwardedHeadersFilterTests
//so first get uris, then extract paths and remove one from another if it's the ending part

LinkedHashSet<URI> originalUris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);

if(originalUris != null && requestUri != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment with a general overview of the algorithm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added now


originalUris.stream().forEach(originalUri -> {

if(originalUri!=null && originalUri.getPath()!=null) {
String prefix = originalUri.getPath();

//strip trailing slashes before checking if request path is end of original path
String originalUriPath = originalUri.getPath().substring(0, originalUri.getPath().length() - (originalUri.getPath().endsWith("/") ? 1 : 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do a substring if path doesn't end with /. Refactor to something like

	private String stripTrailingSlash(URI uri) {
		if (uri.getPath().endsWith("/")) {
			return uri.getPath().substring(0, uri.getPath().length() - 1);
		} else {
			return uri.getPath();
		}
	}

String requestUriPath = requestUri.getPath().substring(0, requestUri.getPath().length() - (requestUri.getPath().endsWith("/") ? 1 : 0));

if(requestUriPath!=null && (originalUriPath.endsWith(requestUriPath))) {
prefix = originalUriPath.replace(requestUriPath, "");
}
if (prefix != null && prefix.length() > 0 &&
prefix.length() < originalUri.getPath().length()) {
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, isPrefixAppend());
}
}
});
}
}

if (isPortEnabled()) {
String port = String.valueOf(request.getURI().getPort());
if (request.getURI().getPort() < 0) {
Expand Down Expand Up @@ -238,4 +301,4 @@ private String toHostHeader(ServerHttpRequest request) {
return host + ":" + port;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.LinkedHashSet;

import org.junit.Test;

import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_FOR_HEADER;
import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER;
import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER;
import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PREFIX_HEADER;
import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PROTO_HEADER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

/**
* @author Spencer Gibb
Expand Down Expand Up @@ -112,23 +119,106 @@ public void appendDisabled() throws Exception {
.header(X_FORWARDED_HOST_HEADER, "example.com")
.header(X_FORWARDED_PORT_HEADER, "443")
.header(X_FORWARDED_PROTO_HEADER, "https")
.header(X_FORWARDED_PREFIX_HEADER,"/prefix")
.build();

XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
filter.setForAppend(false);
filter.setHostAppend(false);
filter.setPortAppend(false);
filter.setProtoAppend(false);
filter.setPrefixAppend(false);

HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));

assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER,
X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER);
X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER,X_FORWARDED_PREFIX_HEADER);

assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("10.0.0.1");
assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost:8080");
assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("8080");
assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http");
assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/prefix");
}


@Test
public void prefixToInfer() throws Exception {
MockServerHttpRequest request = MockServerHttpRequest
.get("http://originalhost:8080/prefix/get")
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
.build();

XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
filter.setPrefixAppend(true);
filter.setPrefixEnabled(true);

ServerWebExchange exchange = MockServerWebExchange.from(request);
LinkedHashSet<URI> originalUris = new LinkedHashSet<>();
originalUris.add(UriComponentsBuilder.fromUriString("http://originalhost:8080/prefix/get/").build().toUri()); //trailing slash
exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris);
URI requestUri = UriComponentsBuilder.fromUriString("http://routedservice:8090/get").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);

HttpHeaders headers = filter.filter(request.getHeaders(), exchange);

assertThat(headers).containsKeys(X_FORWARDED_PREFIX_HEADER);

assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/prefix");
}

@Test
public void noPrefixToInfer() throws Exception {
MockServerHttpRequest request = MockServerHttpRequest
.get("http://originalhost:8080/get")
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
.build();

XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
filter.setPrefixAppend(true);
filter.setPrefixEnabled(true);
filter.setForEnabled(false);
filter.setHostEnabled(false);
filter.setPortEnabled(false);
filter.setProtoEnabled(false);

ServerWebExchange exchange = MockServerWebExchange.from(request);
LinkedHashSet<URI> originalUris = new LinkedHashSet<>();
originalUris.add(UriComponentsBuilder.fromUriString("http://originalhost:8080/get/").build().toUri());
exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris);
URI requestUri = UriComponentsBuilder.fromUriString("http://routedservice:8090/get").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);

HttpHeaders headers = filter.filter(request.getHeaders(), exchange);

assertThat(headers).isEmpty();
}

@Test
public void routedPathInRequestPathButNotPrefix() throws Exception {
MockServerHttpRequest request = MockServerHttpRequest
.get("http://originalhost:8080/get")
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
.build();

XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
filter.setPrefixAppend(true);
filter.setPrefixEnabled(true);
filter.setForEnabled(false);
filter.setHostEnabled(false);
filter.setPortEnabled(false);
filter.setProtoEnabled(false);

ServerWebExchange exchange = MockServerWebExchange.from(request);
LinkedHashSet<URI> originalUris = new LinkedHashSet<>();
originalUris.add(UriComponentsBuilder.fromUriString("http://originalhost:8080/one/two/three").build().toUri());
exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris);
URI requestUri = UriComponentsBuilder.fromUriString("http://routedservice:8090/two").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);

HttpHeaders headers = filter.filter(request.getHeaders(), exchange);

assertThat(headers).isEmpty();
}

@Test
Expand All @@ -143,6 +233,7 @@ public void allDisabled() throws Exception {
filter.setHostEnabled(false);
filter.setPortEnabled(false);
filter.setProtoEnabled(false);
filter.setPrefixEnabled(false);

HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));

Expand Down