Merge pull request #1042 from thirunar/2.1.x

Fixes gh-878 - secure headers can be disabled #1041
This commit is contained in:
Ryan Baxter
2019-06-04 19:41:15 -04:00
committed by GitHub
4 changed files with 176 additions and 16 deletions

View File

@@ -790,7 +790,7 @@ spring:
If you are integrating https://projects.spring.io/spring-security/[Spring Security] with Spring Session, and want to ensure security details have been forwarded to the remote process, this is critical.
=== SecureHeaders GatewayFilter Factory
The SecureHeaders GatewayFilter Factory adds a number of headers to the response at the reccomendation from https://blog.appcanary.com/2017/http-security-headers.html[this blog post].
The SecureHeaders GatewayFilter Factory adds a number of headers to the response at the recommendation from https://blog.appcanary.com/2017/http-security-headers.html[this blog post].
.The following headers are added (allong with default values):
* `X-Xss-Protection:1; mode=block`
@@ -814,6 +814,10 @@ To change the default values set the appropriate property in the `spring.cloud.g
* `download-options`
* `permitted-cross-domain-policies`
To disable the default values set the property `spring.cloud.gateway.filter.secure-headers.disable` with comma separated values.
.Example:
`spring.cloud.gateway.filter.secure-headers.disable=frame-options,download-options`
=== SetPath GatewayFilter Factory
The SetPath GatewayFilter Factory takes a path `template` parameter. It offers a simple way to manipulate the request path by allowing templated segments of the path. This uses the uri templates from Spring Framework. Multiple matching segments are allowed.

View File

@@ -16,13 +16,15 @@
package org.springframework.cloud.gateway.filter.factory;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.HttpHeaders;
/**
* https://blog.appcanary.com/2017/http-security-headers.html.
*
* @author Spencer Gibb
* @author Spencer Gibb, Thirunavukkarasu Ravichandran
*/
public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFactory {
@@ -79,22 +81,50 @@ public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFact
return (exchange, chain) -> {
HttpHeaders headers = exchange.getResponse().getHeaders();
// TODO: allow header to be disabled
headers.add(X_XSS_PROTECTION_HEADER, properties.getXssProtectionHeader());
headers.add(STRICT_TRANSPORT_SECURITY_HEADER,
properties.getStrictTransportSecurity());
headers.add(X_FRAME_OPTIONS_HEADER, properties.getFrameOptions());
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER,
properties.getContentTypeOptions());
headers.add(REFERRER_POLICY_HEADER, properties.getReferrerPolicy());
headers.add(CONTENT_SECURITY_POLICY_HEADER,
properties.getContentSecurityPolicy());
headers.add(X_DOWNLOAD_OPTIONS_HEADER, properties.getDownloadOptions());
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER,
properties.getPermittedCrossDomainPolicies());
List<String> disabled = properties.getDisable();
if (isEnabled(disabled, X_XSS_PROTECTION_HEADER)) {
headers.add(X_XSS_PROTECTION_HEADER, properties.getXssProtectionHeader());
}
if (isEnabled(disabled, STRICT_TRANSPORT_SECURITY_HEADER)) {
headers.add(STRICT_TRANSPORT_SECURITY_HEADER,
properties.getStrictTransportSecurity());
}
if (isEnabled(disabled, X_FRAME_OPTIONS_HEADER)) {
headers.add(X_FRAME_OPTIONS_HEADER, properties.getFrameOptions());
}
if (isEnabled(disabled, X_CONTENT_TYPE_OPTIONS_HEADER)) {
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER,
properties.getContentTypeOptions());
}
if (isEnabled(disabled, REFERRER_POLICY_HEADER)) {
headers.add(REFERRER_POLICY_HEADER, properties.getReferrerPolicy());
}
if (isEnabled(disabled, CONTENT_SECURITY_POLICY_HEADER)) {
headers.add(CONTENT_SECURITY_POLICY_HEADER,
properties.getContentSecurityPolicy());
}
if (isEnabled(disabled, X_DOWNLOAD_OPTIONS_HEADER)) {
headers.add(X_DOWNLOAD_OPTIONS_HEADER, properties.getDownloadOptions());
}
if (isEnabled(disabled, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) {
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER,
properties.getPermittedCrossDomainPolicies());
}
return chain.filter(exchange);
};
}
private boolean isEnabled(List<String> disabledHeaders, String header) {
return !disabledHeaders.contains(header.toLowerCase());
}
}

View File

@@ -16,10 +16,13 @@
package org.springframework.cloud.gateway.filter.factory;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
* @author Spencer Gibb, Thirunavukkarasu Ravichandran
*/
@ConfigurationProperties("spring.cloud.gateway.filter.secure-headers")
public class SecureHeadersProperties {
@@ -95,6 +98,8 @@ public class SecureHeadersProperties {
private String permittedCrossDomainPolicies = X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER_DEFAULT;
private List<String> disable = new ArrayList<>();
public String getXssProtectionHeader() {
return xssProtectionHeader;
}
@@ -159,6 +164,14 @@ public class SecureHeadersProperties {
this.permittedCrossDomainPolicies = permittedCrossDomainPolicies;
}
public List<String> getDisable() {
return disable;
}
public void setDisable(List<String> disable) {
this.disable = disable;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("SecureHeadersProperties{");
@@ -172,6 +185,7 @@ public class SecureHeadersProperties {
sb.append(", downloadOptions='").append(downloadOptions).append('\'');
sb.append(", permittedCrossDomainPolicies='").append(permittedCrossDomainPolicies)
.append('\'');
sb.append(", disabled='").append(disable).append('\'');
sb.append('}');
return sb.toString();
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.CONTENT_SECURITY_POLICY_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.REFERRER_POLICY_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.STRICT_TRANSPORT_SECURITY_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_CONTENT_TYPE_OPTIONS_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_DOWNLOAD_OPTIONS_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_FRAME_OPTIONS_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER;
import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_XSS_PROTECTION_HEADER;
/**
* @author Thirunavukkarasu Ravichandran
*/
public class SecureHeadersGatewayFilterFactoryUnitTests {
private GatewayFilter filter;
private ServerWebExchange exchange;
private GatewayFilterChain filterChain;
private ArgumentCaptor<ServerWebExchange> captor;
@Before
public void setUp() {
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.build();
exchange = MockServerWebExchange.from(request);
filterChain = mock(GatewayFilterChain.class);
captor = ArgumentCaptor.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
}
@Test
public void addAllHeadersIfNothingIsDisabled() {
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
new SecureHeadersProperties());
NameConfig config = new NameConfig();
config.setName("SecureHeadersGatewayFilter");
filter = filterFactory.apply(config);
filter.filter(exchange, filterChain);
ServerHttpResponse response = captor.getValue().getResponse();
assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER,
STRICT_TRANSPORT_SECURITY_HEADER, X_FRAME_OPTIONS_HEADER,
X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER,
X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
}
@Test
public void doNotAddDisabledHeaders() {
SecureHeadersProperties properties = new SecureHeadersProperties();
properties.setDisable(asList("x-xss-protection", "strict-transport-security",
"x-frame-options", "x-content-type-options", "referrer-policy",
"content-security-policy", "x-download-options",
"x-permitted-cross-domain-policies"));
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
properties);
NameConfig config = new NameConfig();
config.setName("SecureHeadersGatewayFilter");
filter = filterFactory.apply(config);
filter.filter(exchange, filterChain);
ServerHttpResponse response = captor.getValue().getResponse();
assertThat(response.getHeaders()).doesNotContainKeys(X_XSS_PROTECTION_HEADER,
STRICT_TRANSPORT_SECURITY_HEADER, X_FRAME_OPTIONS_HEADER,
X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER,
CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER,
X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER);
}
}