Add global zuul sensitiveHeaders option.

Route specific sensitiveHeaders override global. fixes gh-944
parent 4479a2b2
...@@ -1267,6 +1267,8 @@ route, e.g. ...@@ -1267,6 +1267,8 @@ route, e.g.
url: https://dowstream url: https://dowstream
---- ----
Sensitive headers can also be set globally setting `zuul.sensitiveHeaders`. If `sensitiveHeaders` is set on a route, this will override the global `sensitiveHeaders` setting.
NOTE: this is the default value for `sensitiveHeaders`, so you don't NOTE: this is the default value for `sensitiveHeaders`, so you don't
need to set it unless you want it to be different. N.B. this is new in need to set it unless you want it to be different. N.B. this is new in
Spring Cloud Netflix 1.1 (in 1.0 the user had no control over headers Spring Cloud Netflix 1.1 (in 1.0 the user had no control over headers
......
...@@ -115,6 +115,17 @@ public class ZuulProperties { ...@@ -115,6 +115,17 @@ public class ZuulProperties {
*/ */
private boolean removeSemicolonContent = true; private boolean removeSemicolonContent = true;
/**
* List of sensitive headers that are not passed to downstream requests. Defaults
* to a "safe" set of headers that commonly contain user credentials. It's OK to
* remove those from the list if the downstream service is part of the same system
* as the proxy, so they are sharing authentication data. If using a physical URL
* outside your own domain, then generally it would be a bad idea to leak user
* credentials.
*/
private Set<String> sensitiveHeaders = new LinkedHashSet<>(
Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
public Set<String> getIgnoredHeaders() { public Set<String> getIgnoredHeaders() {
Set<String> ignoredHeaders = new LinkedHashSet<>(this.ignoredHeaders); Set<String> ignoredHeaders = new LinkedHashSet<>(this.ignoredHeaders);
if (ClassUtils.isPresent( if (ClassUtils.isPresent(
...@@ -193,8 +204,7 @@ public class ZuulProperties { ...@@ -193,8 +204,7 @@ public class ZuulProperties {
* outside your own domain, then generally it would be a bad idea to leak user * outside your own domain, then generally it would be a bad idea to leak user
* credentials. * credentials.
*/ */
private Set<String> sensitiveHeaders = new LinkedHashSet<>( private Set<String> sensitiveHeaders = new LinkedHashSet<>();
Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
public ZuulRoute(String text) { public ZuulRoute(String text) {
String location = null; String location = null;
......
...@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.zuul.filters.pre; ...@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.zuul.filters.pre;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
...@@ -37,21 +38,18 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -37,21 +38,18 @@ public class PreDecorationFilter extends ZuulFilter {
private RouteLocator routeLocator; private RouteLocator routeLocator;
private boolean addProxyHeaders;
private String dispatcherServletPath; private String dispatcherServletPath;
private String zuulServletPath;
private ZuulProperties properties;
private UrlPathHelper urlPathHelper = new UrlPathHelper(); private UrlPathHelper urlPathHelper = new UrlPathHelper();
public PreDecorationFilter(RouteLocator routeLocator, public PreDecorationFilter(RouteLocator routeLocator,
String dispatcherServletPath, ZuulProperties zuulProperties) { String dispatcherServletPath, ZuulProperties properties) {
this.routeLocator = routeLocator; this.routeLocator = routeLocator;
this.addProxyHeaders = zuulProperties.isAddProxyHeaders(); this.properties = properties;
this.urlPathHelper.setRemoveSemicolonContent(zuulProperties.isRemoveSemicolonContent()); this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
this.dispatcherServletPath = dispatcherServletPath; this.dispatcherServletPath = dispatcherServletPath;
this.zuulServletPath = zuulProperties.getServletPath();
} }
@Override @Override
...@@ -67,9 +65,8 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -67,9 +65,8 @@ public class PreDecorationFilter extends ZuulFilter {
@Override @Override
public boolean shouldFilter() { public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey("forward.to") // another filter has already forwarded return !ctx.containsKey("forward.to") // a filter has already forwarded
&& !ctx.containsKey("serviceId"); // another filter has already determined && !ctx.containsKey("serviceId"); // a filter has already determined serviceId
// serviceId
} }
@Override @Override
...@@ -83,7 +80,11 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -83,7 +80,11 @@ public class PreDecorationFilter extends ZuulFilter {
if (location != null) { if (location != null) {
ctx.put("requestURI", route.getPath()); ctx.put("requestURI", route.getPath());
ctx.put("proxy", route.getId()); ctx.put("proxy", route.getId());
ctx.put("ignoredHeaders", route.getSensitiveHeaders()); if (route.getSensitiveHeaders().isEmpty()) {
ctx.put(ProxyRequestHelper.IGNORED_HEADERS, this.properties.getSensitiveHeaders());
} else {
ctx.put(ProxyRequestHelper.IGNORED_HEADERS, route.getSensitiveHeaders());
}
if (route.getRetryable() != null) { if (route.getRetryable() != null) {
ctx.put("retryable", route.getRetryable()); ctx.put("retryable", route.getRetryable());
...@@ -105,7 +106,7 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -105,7 +106,7 @@ public class PreDecorationFilter extends ZuulFilter {
ctx.setRouteHost(null); ctx.setRouteHost(null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", location); ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
} }
if (this.addProxyHeaders) { if (this.properties.isAddProxyHeaders()) {
ctx.addZuulRequestHeader("X-Forwarded-Host", ctx.addZuulRequestHeader("X-Forwarded-Host",
ctx.getRequest().getServerName()); ctx.getRequest().getServerName());
ctx.addZuulRequestHeader("X-Forwarded-Port", ctx.addZuulRequestHeader("X-Forwarded-Port",
...@@ -135,8 +136,8 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -135,8 +136,8 @@ public class PreDecorationFilter extends ZuulFilter {
if (RequestUtils.isZuulServletRequest()) { if (RequestUtils.isZuulServletRequest()) {
//remove the Zuul servletPath from the requestUri //remove the Zuul servletPath from the requestUri
log.debug("zuulServletPath=" + zuulServletPath); log.debug("zuulServletPath=" + this.properties.getServletPath());
fallBackUri = fallBackUri.replaceFirst(zuulServletPath, ""); fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
log.debug("Replaced Zuul servlet path:" + fallBackUri); log.debug("Replaced Zuul servlet path:" + fallBackUri);
} else { } else {
//remove the DispatcherServlet servletPath from the requestUri //remove the DispatcherServlet servletPath from the requestUri
......
...@@ -16,8 +16,11 @@ ...@@ -16,8 +16,11 @@
package org.springframework.cloud.netflix.zuul.filters; package org.springframework.cloud.netflix.zuul.filters;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
...@@ -29,7 +32,17 @@ import static org.junit.Assert.assertTrue; ...@@ -29,7 +32,17 @@ import static org.junit.Assert.assertTrue;
*/ */
public class ZuulPropertiesTests { public class ZuulPropertiesTests {
private ZuulProperties zuul = new ZuulProperties(); private ZuulProperties zuul;
@Before
public void setup() {
this.zuul = new ZuulProperties();
}
@After
public void teardown() {
this.zuul = null;
}
@Test @Test
public void defaultIgnoredHeaders() { public void defaultIgnoredHeaders() {
...@@ -46,17 +59,22 @@ public class ZuulPropertiesTests { ...@@ -46,17 +59,22 @@ public class ZuulPropertiesTests {
public void defaultSensitiveHeaders() { public void defaultSensitiveHeaders() {
ZuulRoute route = new ZuulRoute("foo"); ZuulRoute route = new ZuulRoute("foo");
this.zuul.getRoutes().put("foo", route); this.zuul.getRoutes().put("foo", route);
assertTrue(this.zuul.getRoutes().get("foo").getSensitiveHeaders() assertTrue(this.zuul.getRoutes().get("foo").getSensitiveHeaders().isEmpty());
.contains("Cookie")); assertTrue(this.zuul.getSensitiveHeaders().containsAll(
Arrays.asList("Cookie", "Set-Cookie", "Authorization")));
} }
@Test @Test
public void addSensitiveHeaders() { public void addSensitiveHeaders() {
this.zuul.setSensitiveHeaders(Collections.singleton("x-bar"));
ZuulRoute route = new ZuulRoute("foo"); ZuulRoute route = new ZuulRoute("foo");
route.setSensitiveHeaders(Collections.singleton("x-foo")); route.setSensitiveHeaders(Collections.singleton("x-foo"));
this.zuul.getRoutes().put("foo", route); this.zuul.getRoutes().put("foo", route);
assertFalse(this.zuul.getRoutes().get("foo").getSensitiveHeaders() ZuulRoute foo = this.zuul.getRoutes().get("foo");
.contains("Cookie")); assertTrue(foo.getSensitiveHeaders().contains("x-foo"));
assertFalse(foo.getSensitiveHeaders().contains("Cookie"));
assertTrue(this.zuul.getSensitiveHeaders().contains("x-bar"));
assertFalse(this.zuul.getSensitiveHeaders().contains("Cookie"));
} }
} }
...@@ -16,12 +16,15 @@ ...@@ -16,12 +16,15 @@
package org.springframework.cloud.netflix.zuul.filters.pre; package org.springframework.cloud.netflix.zuul.filters.pre;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
...@@ -31,6 +34,8 @@ import com.netflix.util.Pair; ...@@ -31,6 +34,8 @@ import com.netflix.util.Pair;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks; import static org.mockito.MockitoAnnotations.initMocks;
/** /**
...@@ -238,6 +243,38 @@ public class PreDecorationFilterTests { ...@@ -238,6 +243,38 @@ public class PreDecorationFilterTests {
assertEquals("/special/api/bar/1", ctx.get("forward.to")); assertEquals("/special/api/bar/1", ctx.get("forward.to"));
} }
@Test
public void sensitiveHeadersOverride() throws Exception {
this.properties.setPrefix("/api");
this.properties.setStripPrefix(true);
this.properties.setSensitiveHeaders(Collections.singleton("x-bar"));
this.request.setRequestURI("/api/foo/1");
ZuulRoute route = new ZuulRoute("/foo/**", "foo");
route.setSensitiveHeaders(Collections.singleton("x-foo"));
this.routeLocator.addRoute(route);
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
@SuppressWarnings("unchecked")
Set<String> sensitiveHeaders = (Set<String>) ctx.get(ProxyRequestHelper.IGNORED_HEADERS);
assertTrue("sensitiveHeaders is wrong", sensitiveHeaders.containsAll(Collections.singletonList("x-foo")));
assertFalse("sensitiveHeaders is wrong", sensitiveHeaders.contains("Cookie"));
}
@Test
public void sensitiveHeadersDefaults() throws Exception {
this.properties.setPrefix("/api");
this.properties.setStripPrefix(true);
this.properties.setSensitiveHeaders(Collections.singleton("x-bar"));
this.request.setRequestURI("/api/foo/1");
this.routeLocator.addRoute("/foo/**", "foo");
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
@SuppressWarnings("unchecked")
Set<String> sensitiveHeaders = (Set<String>) ctx.get(ProxyRequestHelper.IGNORED_HEADERS);
assertTrue("sensitiveHeaders is wrong", sensitiveHeaders.containsAll(Collections.singletonList("x-bar")));
assertFalse("sensitiveHeaders is wrong", sensitiveHeaders.contains("Cookie"));
}
private Object getHeader(List<Pair<String, String>> headers, String key) { private Object getHeader(List<Pair<String, String>> headers, String key) {
String value = null; String value = null;
for (Pair<String, String> pair : headers) { for (Pair<String, String> pair : headers) {
......
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