From 40361f1b8ee2fcb9effd553c1d611064869bd44e Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 25 Feb 2016 12:57:33 +0000 Subject: [PATCH] Add whitelist hosts and sensitive headers --- .../main/asciidoc/spring-cloud-netflix.adoc | 9 ++++ .../zuul/filters/ProxyRequestHelper.java | 34 ++++++++++++++- .../netflix/zuul/filters/ZuulProperties.java | 17 ++++++++ .../route/SimpleHostRoutingFilter.java | 5 ++- .../zuul/filters/ProxyRequestHelperTests.java | 42 ++++++++++++++++--- 5 files changed, 98 insertions(+), 9 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index ebcc211d..27b933f0 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -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. 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 A common pattern when migrating an existing application or API is to diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java index b8fc3a66..1b05f5d3 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java @@ -19,8 +19,10 @@ package org.springframework.cloud.netflix.zuul.filters; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URL; import java.nio.charset.Charset; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -37,6 +39,7 @@ import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.PatternMatchUtils; import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriUtils; import org.springframework.web.util.WebUtils; @@ -65,8 +68,20 @@ public class ProxyRequestHelper { private Set ignoredHeaders = new LinkedHashSet<>(); + private Set sensitiveHeaders = new LinkedHashSet<>(); + + private Set whitelistHosts = new LinkedHashSet<>(); + + public void setWhitelistHosts(Set whitelistHosts) { + this.whitelistHosts.addAll(whitelistHosts); + } + + public void setSensitiveHeaders(Set sensitiveHeaders) { + this.sensitiveHeaders.addAll(sensitiveHeaders); + } + public void setIgnoredHeaders(Set ignoredHeaders) { - this.ignoredHeaders = ignoredHeaders; + this.ignoredHeaders.addAll(ignoredHeaders); } public void setTraces(TraceRepository traces) { @@ -182,11 +197,28 @@ public class ProxyRequestHelper { for (String name : this.ignoredHeaders) { set.add(name.toLowerCase()); } + for (String name : getSensitiveHeaders(ctx)) { + set.add(name.toLowerCase()); + } for (String name : names) { set.add(name.toLowerCase()); } } + private Collection 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) { String name = headerName.toLowerCase(); RequestContext ctx = RequestContext.getCurrentContext(); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java index b9f4ae99..19b6124d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java @@ -66,6 +66,8 @@ public class ZuulProperties { private Set ignoredHeaders = new LinkedHashSet<>(); + private Sensitive sensitive = new Sensitive(); + private String servletPath = "/zuul"; private boolean ignoreLocalService = true; @@ -103,6 +105,21 @@ public class ZuulProperties { } } + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Sensitive { + /** + * Headers that are considered sensitive, and not passed on through a proxy. + */ + private Set 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 whitelist = new LinkedHashSet<>(Arrays.asList("localhost")); + } + @Data @AllArgsConstructor @NoArgsConstructor diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java index 83013f2a..90a5e855 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java @@ -209,9 +209,10 @@ public class SimpleHostRoutingFilter extends ZuulFilter { .build(); this.connectionManager = new PoolingHttpClientConnectionManager(registry); - this.connectionManager.setMaxTotal(hostProperties.getMaxTotalConnections()); this.connectionManager - .setDefaultMaxPerRoute(hostProperties.getMaxPerRouteConnections()); + .setMaxTotal(this.hostProperties.getMaxTotalConnections()); + this.connectionManager.setDefaultMaxPerRoute( + this.hostProperties.getMaxPerRouteConnections()); return this.connectionManager; } catch (Exception ex) { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java index dcc6da35..b36c4004 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java @@ -17,10 +17,9 @@ package org.springframework.cloud.netflix.zuul.filters; import java.io.IOException; -import java.util.Collection; +import java.net.URL; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -28,7 +27,6 @@ import org.mockito.Mock; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.Trace; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -163,7 +161,7 @@ public class ProxyRequestHelperTests { } @Test - public void getQueryString(){ + public void getQueryString() { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("a", "1234"); params.add("b", "5678"); @@ -174,7 +172,7 @@ public class ProxyRequestHelperTests { } @Test - public void getQueryStringWithEmptyParam(){ + public void getQueryStringWithEmptyParam() { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("wsdl", ""); @@ -182,4 +180,36 @@ public class ProxyRequestHelperTests { 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)); + } }