Merge branch '2.2.x'

This commit is contained in:
spencergibb
2020-11-02 14:51:51 -05:00
3 changed files with 207 additions and 23 deletions

View File

@@ -32,7 +32,8 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi
*
* @author Spencer Gibb, Thirunavukkarasu Ravichandran
*/
public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFactory {
public class SecureHeadersGatewayFilterFactory
extends AbstractGatewayFilterFactory<SecureHeadersGatewayFilterFactory.Config> {
/**
* Xss-Protection header name.
@@ -77,50 +78,54 @@ public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFact
private final SecureHeadersProperties properties;
public SecureHeadersGatewayFilterFactory(SecureHeadersProperties properties) {
super(Config.class);
this.properties = properties;
}
@Override
public GatewayFilter apply(Object config) {
// TODO: allow args to override properties
public GatewayFilter apply(Config originalConfig) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getResponse().getHeaders();
List<String> disabled = properties.getDisable();
Config config = originalConfig.withDefaults(properties);
if (isEnabled(disabled, X_XSS_PROTECTION_HEADER)) {
headers.add(X_XSS_PROTECTION_HEADER, properties.getXssProtectionHeader());
headers.add(X_XSS_PROTECTION_HEADER, config.getXssProtectionHeader());
}
if (isEnabled(disabled, STRICT_TRANSPORT_SECURITY_HEADER)) {
headers.add(STRICT_TRANSPORT_SECURITY_HEADER, properties.getStrictTransportSecurity());
headers.add(STRICT_TRANSPORT_SECURITY_HEADER,
config.getStrictTransportSecurity());
}
if (isEnabled(disabled, X_FRAME_OPTIONS_HEADER)) {
headers.add(X_FRAME_OPTIONS_HEADER, properties.getFrameOptions());
headers.add(X_FRAME_OPTIONS_HEADER, config.getFrameOptions());
}
if (isEnabled(disabled, X_CONTENT_TYPE_OPTIONS_HEADER)) {
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER, properties.getContentTypeOptions());
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER,
config.getContentTypeOptions());
}
if (isEnabled(disabled, REFERRER_POLICY_HEADER)) {
headers.add(REFERRER_POLICY_HEADER, properties.getReferrerPolicy());
headers.add(REFERRER_POLICY_HEADER, config.getReferrerPolicy());
}
if (isEnabled(disabled, CONTENT_SECURITY_POLICY_HEADER)) {
headers.add(CONTENT_SECURITY_POLICY_HEADER, properties.getContentSecurityPolicy());
headers.add(CONTENT_SECURITY_POLICY_HEADER,
config.getContentSecurityPolicy());
}
if (isEnabled(disabled, X_DOWNLOAD_OPTIONS_HEADER)) {
headers.add(X_DOWNLOAD_OPTIONS_HEADER, properties.getDownloadOptions());
headers.add(X_DOWNLOAD_OPTIONS_HEADER, config.getDownloadOptions());
}
if (isEnabled(disabled, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) {
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER, properties.getPermittedCrossDomainPolicies());
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER,
config.getPermittedCrossDomainPolicies());
}
return chain.filter(exchange);
@@ -137,4 +142,134 @@ public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFact
return !disabledHeaders.contains(header.toLowerCase());
}
public static class Config {
private String xssProtectionHeader;
private String strictTransportSecurity;
private String frameOptions;
private String contentTypeOptions;
private String referrerPolicy;
private String contentSecurityPolicy;
private String downloadOptions;
private String permittedCrossDomainPolicies;
public Config withDefaults(SecureHeadersProperties properties) {
Config config = new Config();
config.setXssProtectionHeader(xssProtectionHeader);
config.setStrictTransportSecurity(strictTransportSecurity);
config.setFrameOptions(frameOptions);
config.setContentTypeOptions(contentTypeOptions);
config.setReferrerPolicy(referrerPolicy);
config.setContentSecurityPolicy(contentSecurityPolicy);
config.setDownloadOptions(downloadOptions);
config.setPermittedCrossDomainPolicies(permittedCrossDomainPolicies);
if (config.xssProtectionHeader == null) {
config.xssProtectionHeader = properties.getXssProtectionHeader();
}
if (config.strictTransportSecurity == null) {
config.strictTransportSecurity = properties.getStrictTransportSecurity();
}
if (config.frameOptions == null) {
config.frameOptions = properties.getFrameOptions();
}
if (config.contentTypeOptions == null) {
config.contentTypeOptions = properties.getContentTypeOptions();
}
if (config.referrerPolicy == null) {
config.referrerPolicy = properties.getReferrerPolicy();
}
if (config.contentSecurityPolicy == null) {
config.contentSecurityPolicy = properties.getContentSecurityPolicy();
}
if (config.downloadOptions == null) {
config.downloadOptions = properties.getDownloadOptions();
}
if (config.permittedCrossDomainPolicies == null) {
config.permittedCrossDomainPolicies = properties
.getPermittedCrossDomainPolicies();
}
return config;
}
public String getXssProtectionHeader() {
return xssProtectionHeader;
}
public void setXssProtectionHeader(String xssProtectionHeader) {
this.xssProtectionHeader = xssProtectionHeader;
}
public String getStrictTransportSecurity() {
return strictTransportSecurity;
}
public void setStrictTransportSecurity(String strictTransportSecurity) {
this.strictTransportSecurity = strictTransportSecurity;
}
public String getFrameOptions() {
return frameOptions;
}
public void setFrameOptions(String frameOptions) {
this.frameOptions = frameOptions;
}
public String getContentTypeOptions() {
return contentTypeOptions;
}
public void setContentTypeOptions(String contentTypeOptions) {
this.contentTypeOptions = contentTypeOptions;
}
public String getReferrerPolicy() {
return referrerPolicy;
}
public void setReferrerPolicy(String referrerPolicy) {
this.referrerPolicy = referrerPolicy;
}
public String getContentSecurityPolicy() {
return contentSecurityPolicy;
}
public void setContentSecurityPolicy(String contentSecurityPolicy) {
this.contentSecurityPolicy = contentSecurityPolicy;
}
public String getDownloadOptions() {
return downloadOptions;
}
public void setDownloadOptions(String downloadOptions) {
this.downloadOptions = downloadOptions;
}
public String getPermittedCrossDomainPolicies() {
return permittedCrossDomainPolicies;
}
public void setPermittedCrossDomainPolicies(String permittedCrossDomainPolicies) {
this.permittedCrossDomainPolicies = permittedCrossDomainPolicies;
}
}
}

View File

@@ -523,12 +523,24 @@ public class GatewayFilterSpec extends UriSpec {
* post</a>.
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
*/
@SuppressWarnings("unchecked")
public GatewayFilterSpec secureHeaders() {
return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(c -> {
return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(config -> {
}));
}
/**
* A filter that adds a number of headers to the response at the reccomendation from
* <a href="https://blog.appcanary.com/2017/http-security-headers.html">this blog
* post</a>.
* @param configConsumer self define headers
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
*/
public GatewayFilterSpec secureHeaders(
Consumer<SecureHeadersGatewayFilterFactory.Config> configConsumer) {
return filter(
getBean(SecureHeadersGatewayFilterFactory.class).apply(configConsumer));
}
/**
* A filter that sets the path of the request before it is routed by the Gateway.
* @param template the path to set on the request, allows multiple matching segments

View File

@@ -24,7 +24,6 @@ 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;
@@ -35,6 +34,7 @@ import static org.assertj.core.api.Assertions.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.Config;
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;
@@ -70,9 +70,7 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
public void addAllHeadersIfNothingIsDisabled() {
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
new SecureHeadersProperties());
NameConfig config = new NameConfig();
config.setName("SecureHeadersGatewayFilter");
filter = filterFactory.apply(config);
filter = filterFactory.apply(new Config());
filter.filter(exchange, filterChain);
@@ -89,10 +87,9 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
"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);
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
properties);
filter = filterFactory.apply(new Config());
filter.filter(exchange, filterChain);
@@ -103,9 +100,49 @@ public class SecureHeadersGatewayFilterFactoryUnitTests {
}
@Test
public void overrideSomeHeaders() {
SecureHeadersProperties properties = new SecureHeadersProperties();
SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(
new SecureHeadersProperties());
Config config = new Config();
config.setStrictTransportSecurity("max-age=65535");
config.setReferrerPolicy("referrer");
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);
assertThat(response.getHeaders().get(STRICT_TRANSPORT_SECURITY_HEADER))
.containsOnly("max-age=65535");
assertThat(response.getHeaders().get(REFERRER_POLICY_HEADER))
.containsOnly("referrer");
assertThat(response.getHeaders().get(X_XSS_PROTECTION_HEADER))
.containsOnly(properties.getXssProtectionHeader());
assertThat(response.getHeaders().get(X_FRAME_OPTIONS_HEADER))
.containsOnly(properties.getFrameOptions());
assertThat(response.getHeaders().get(X_CONTENT_TYPE_OPTIONS_HEADER))
.containsOnly(properties.getContentTypeOptions());
assertThat(response.getHeaders().get(CONTENT_SECURITY_POLICY_HEADER))
.containsOnly(properties.getContentSecurityPolicy());
assertThat(response.getHeaders().get(X_DOWNLOAD_OPTIONS_HEADER))
.containsOnly(properties.getDownloadOptions());
assertThat(response.getHeaders().get(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER))
.containsOnly(properties.getPermittedCrossDomainPolicies());
}
@Test
public void toStringFormat() {
GatewayFilter filter = new SecureHeadersGatewayFilterFactory(new SecureHeadersProperties()).apply("");
GatewayFilter filter = new SecureHeadersGatewayFilterFactory(
new SecureHeadersProperties()).apply(new Config());
Assertions.assertThat(filter.toString()).contains("SecureHeaders");
}