Commit 40361f1b by Dave Syer

Add whitelist hosts and sensitive headers

parent 17eb2f8d
...@@ -1227,6 +1227,15 @@ span all services and supersede any other route specification. ...@@ -1227,6 +1227,15 @@ span all services and supersede any other route specification.
This means that all calls such as "/myusers/101" will be forwarded to "/101" on the "users" service. This means that all calls such as "/myusers/101" will be forwarded to "/101" on the "users" service.
But calls including "/admin/" will not resolve. But calls including "/admin/" will not resolve.
=== Sensitive Headers
It's OK to share headers between services in the same system, but you
probably don't want sensitive headers leaking downstream into external
servers. Thus if you use an explicit URL in a route configuration (as
opposed to a service id), then you can also specify a list of
sensitive headers and a whitelist of host patterns to not receive
those headers.
=== Strangulation Patterns and Local Forwards === Strangulation Patterns and Local Forwards
A common pattern when migrating an existing application or API is to A common pattern when migrating an existing application or API is to
......
...@@ -19,8 +19,10 @@ package org.springframework.cloud.netflix.zuul.filters; ...@@ -19,8 +19,10 @@ package org.springframework.cloud.netflix.zuul.filters;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
...@@ -37,6 +39,7 @@ import org.springframework.boot.actuate.trace.TraceRepository; ...@@ -37,6 +39,7 @@ import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils; import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
...@@ -65,8 +68,20 @@ public class ProxyRequestHelper { ...@@ -65,8 +68,20 @@ public class ProxyRequestHelper {
private Set<String> ignoredHeaders = new LinkedHashSet<>(); private Set<String> ignoredHeaders = new LinkedHashSet<>();
private Set<String> sensitiveHeaders = new LinkedHashSet<>();
private Set<String> whitelistHosts = new LinkedHashSet<>();
public void setWhitelistHosts(Set<String> whitelistHosts) {
this.whitelistHosts.addAll(whitelistHosts);
}
public void setSensitiveHeaders(Set<String> sensitiveHeaders) {
this.sensitiveHeaders.addAll(sensitiveHeaders);
}
public void setIgnoredHeaders(Set<String> ignoredHeaders) { public void setIgnoredHeaders(Set<String> ignoredHeaders) {
this.ignoredHeaders = ignoredHeaders; this.ignoredHeaders.addAll(ignoredHeaders);
} }
public void setTraces(TraceRepository traces) { public void setTraces(TraceRepository traces) {
...@@ -182,11 +197,28 @@ public class ProxyRequestHelper { ...@@ -182,11 +197,28 @@ public class ProxyRequestHelper {
for (String name : this.ignoredHeaders) { for (String name : this.ignoredHeaders) {
set.add(name.toLowerCase()); set.add(name.toLowerCase());
} }
for (String name : getSensitiveHeaders(ctx)) {
set.add(name.toLowerCase());
}
for (String name : names) { for (String name : names) {
set.add(name.toLowerCase()); set.add(name.toLowerCase());
} }
} }
private Collection<String> getSensitiveHeaders(RequestContext ctx) {
URL uri = ctx.getRouteHost();
if (uri == null) {
return Collections.emptySet();
}
String host;
host = uri.getHost();
if (PatternMatchUtils.simpleMatch(this.whitelistHosts.toArray(new String[0]),
host)) {
return this.sensitiveHeaders;
}
return Collections.emptySet();
}
public boolean isIncludedHeader(String headerName) { public boolean isIncludedHeader(String headerName) {
String name = headerName.toLowerCase(); String name = headerName.toLowerCase();
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
......
...@@ -66,6 +66,8 @@ public class ZuulProperties { ...@@ -66,6 +66,8 @@ public class ZuulProperties {
private Set<String> ignoredHeaders = new LinkedHashSet<>(); private Set<String> ignoredHeaders = new LinkedHashSet<>();
private Sensitive sensitive = new Sensitive();
private String servletPath = "/zuul"; private String servletPath = "/zuul";
private boolean ignoreLocalService = true; private boolean ignoreLocalService = true;
...@@ -106,6 +108,21 @@ public class ZuulProperties { ...@@ -106,6 +108,21 @@ public class ZuulProperties {
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class Sensitive {
/**
* Headers that are considered sensitive, and not passed on through a proxy.
*/
private Set<String> headers = new LinkedHashSet<>(Arrays.asList("Cookie"));
/**
* Hostname (patterns) that are considered safe and can receive sensitive headers
* when a route is specified as a URL.
*/
private Set<String> whitelist = new LinkedHashSet<>(Arrays.asList("localhost"));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ZuulRoute { public static class ZuulRoute {
private String id; private String id;
......
...@@ -209,9 +209,10 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -209,9 +209,10 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
.build(); .build();
this.connectionManager = new PoolingHttpClientConnectionManager(registry); this.connectionManager = new PoolingHttpClientConnectionManager(registry);
this.connectionManager.setMaxTotal(hostProperties.getMaxTotalConnections());
this.connectionManager this.connectionManager
.setDefaultMaxPerRoute(hostProperties.getMaxPerRouteConnections()); .setMaxTotal(this.hostProperties.getMaxTotalConnections());
this.connectionManager.setDefaultMaxPerRoute(
this.hostProperties.getMaxPerRouteConnections());
return this.connectionManager; return this.connectionManager;
} }
catch (Exception ex) { catch (Exception ex) {
......
...@@ -17,10 +17,9 @@ ...@@ -17,10 +17,9 @@
package org.springframework.cloud.netflix.zuul.filters; package org.springframework.cloud.netflix.zuul.filters;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.net.URL;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -28,7 +27,6 @@ import org.mockito.Mock; ...@@ -28,7 +27,6 @@ import org.mockito.Mock;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.Trace; import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
...@@ -163,7 +161,7 @@ public class ProxyRequestHelperTests { ...@@ -163,7 +161,7 @@ public class ProxyRequestHelperTests {
} }
@Test @Test
public void getQueryString(){ public void getQueryString() {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("a", "1234"); params.add("a", "1234");
params.add("b", "5678"); params.add("b", "5678");
...@@ -174,7 +172,7 @@ public class ProxyRequestHelperTests { ...@@ -174,7 +172,7 @@ public class ProxyRequestHelperTests {
} }
@Test @Test
public void getQueryStringWithEmptyParam(){ public void getQueryStringWithEmptyParam() {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("wsdl", ""); params.add("wsdl", "");
...@@ -182,4 +180,36 @@ public class ProxyRequestHelperTests { ...@@ -182,4 +180,36 @@ public class ProxyRequestHelperTests {
assertThat(queryString, is("?wsdl")); assertThat(queryString, is("?wsdl"));
} }
@Test
public void ignoreSensitiveHeadersMatchingHost() throws Exception {
ProxyRequestHelper helper = new ProxyRequestHelper();
helper.setSensitiveHeaders(Collections.singleton("Cookie"));
helper.setWhitelistHosts(Collections.singleton("*"));
RequestContext context = RequestContext.getCurrentContext();
context.setRouteHost(new URL("http://example.com"));
helper.addIgnoredHeaders();
assertThat(helper.isIncludedHeader("Cookie"), is(false));
}
@Test
public void ignoreSensitiveHeadersNotMatching() throws Exception {
ProxyRequestHelper helper = new ProxyRequestHelper();
helper.setSensitiveHeaders(Collections.singleton("Cookie"));
helper.setWhitelistHosts(Collections.singleton("foo.com"));
RequestContext context = RequestContext.getCurrentContext();
context.setRouteHost(new URL("http://example.com"));
helper.addIgnoredHeaders();
assertThat(helper.isIncludedHeader("Cookie"), is(true));
}
@Test
public void ignoreSensitiveHeadersWhenNoRoute() throws Exception {
ProxyRequestHelper helper = new ProxyRequestHelper();
helper.setSensitiveHeaders(Collections.singleton("Cookie"));
helper.setWhitelistHosts(Collections.singleton("foo.com"));
RequestContext context = RequestContext.getCurrentContext();
helper.addIgnoredHeaders();
assertThat(helper.isIncludedHeader("Cookie"), is(true));
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment