Explorar o código

Add support for Feature-Policy security header

Vedran Pavic %!s(int64=7) %!d(string=hai) anos
pai
achega
c6ea447cc0

+ 46 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.springframework.security.config.annotation.web.configurers;
 
 import java.net.URI;
@@ -58,6 +59,7 @@ import org.springframework.util.Assert;
  * @author Tim Ysewyn
  * @author Joe Grandja
  * @author Eddú Meléndez
+ * @author Vedran Pavic
  * @since 3.2
  */
 public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
@@ -82,6 +84,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 
 	private final ReferrerPolicyConfig referrerPolicy = new ReferrerPolicyConfig();
 
+	private final FeaturePolicyConfig featurePolicy = new FeaturePolicyConfig();
+
 	/**
 	 * Creates a new instance
 	 *
@@ -775,6 +779,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 		addIfNotNull(writers, hpkp.writer);
 		addIfNotNull(writers, contentSecurityPolicy.writer);
 		addIfNotNull(writers, referrerPolicy.writer);
+		addIfNotNull(writers, featurePolicy.writer);
 		writers.addAll(headerWriters);
 		return writers;
 	}
@@ -848,4 +853,44 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 		}
 
 	}
+
+	/**
+	 * Allows configuration for <a href="https://wicg.github.io/feature-policy/">Feature
+	 * Policy</a>.
+	 * <p>
+	 * Calling this method automatically enables (includes) the {@code Feature-Policy}
+	 * header in the response using the supplied policy directive(s).
+	 * <p>
+	 * Configuration is provided to the {@link FeaturePolicyHeaderWriter} which is
+	 * responsible for writing the header.
+	 *
+	 * @see FeaturePolicyHeaderWriter
+	 * @since 5.1
+	 * @return the {@link FeaturePolicyHeaderWriter} for additional configuration
+	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
+	 */
+	public FeaturePolicyConfig featurePolicy(String policyDirectives) {
+		this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
+		return featurePolicy;
+	}
+
+	public final class FeaturePolicyConfig {
+
+		private FeaturePolicyHeaderWriter writer;
+
+		private FeaturePolicyConfig() {
+		}
+
+		/**
+		 * Allows completing configuration of Feature Policy and continuing configuration
+		 * of headers.
+		 *
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
 }

+ 32 - 1
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.springframework.security.config.http;
 
 import java.net.URI;
@@ -47,6 +48,7 @@ import org.w3c.dom.Node;
  * @author Marten Deinum
  * @author Tim Ysewyn
  * @author Eddú Meléndez
+ * @author Vedran Pavic
  * @since 3.2
  */
 public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
@@ -85,6 +87,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 
 	private static final String CONTENT_SECURITY_POLICY_ELEMENT = "content-security-policy";
 	private static final String REFERRER_POLICY_ELEMENT = "referrer-policy";
+	private static final String FEATURE_POLICY_ELEMENT = "feature-policy";
 
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 
@@ -114,6 +117,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 
 		parseReferrerPolicyElement(element, parserContext);
 
+		parseFeaturePolicyElement(element, parserContext);
+
 		parseHeaderElements(element);
 
 		boolean noWriters = headerWriters.isEmpty();
@@ -313,6 +318,32 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		headerWriters.add(headersWriter.getBeanDefinition());
 	}
 
+	private void parseFeaturePolicyElement(Element element, ParserContext context) {
+		Element featurePolicyElement = (element == null) ? null
+				: DomUtils.getChildElementByTagName(element, FEATURE_POLICY_ELEMENT);
+		if (featurePolicyElement != null) {
+			addFeaturePolicy(featurePolicyElement, context);
+		}
+	}
+
+	private void addFeaturePolicy(Element featurePolicyElement, ParserContext context) {
+		BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder
+				.genericBeanDefinition(FeaturePolicyHeaderWriter.class);
+
+		String policyDirectives = featurePolicyElement
+				.getAttribute(ATT_POLICY_DIRECTIVES);
+		if (!StringUtils.hasText(policyDirectives)) {
+			context.getReaderContext().error(
+					ATT_POLICY_DIRECTIVES + " requires a 'value' to be set.",
+					featurePolicyElement);
+		}
+		else {
+			headersWriter.addConstructorArgValue(policyDirectives);
+		}
+
+		headerWriters.add(headersWriter.getBeanDefinition());
+	}
+
 	private void attrNotAllowed(ParserContext context, String attrName,
 			String otherAttrName, Element element) {
 		context.getReaderContext().error(

+ 8 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.1.rnc

@@ -743,7 +743,7 @@ csrf-options.attlist &=
 
 headers =
 ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
-element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & header*)}
+element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & header*)}
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
 	attribute defaults-disabled {xsd:boolean}?
@@ -821,6 +821,13 @@ referrer-options.attlist &=
 	## The policies for the Referrer-Policy header.
 	attribute policy {"no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"}?
 
+feature-policy =
+	## Adds support for Feature Policy
+	element feature-policy {feature-options.attlist}
+feature-options.attlist &=
+	## The security policy directive(s) for the Feature-Policy header.
+	attribute policy-directives {xsd:token}?
+
 cache-control =
 	## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
 	element cache-control {cache-control.attlist}

+ 17 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.1.xsd

@@ -2253,6 +2253,7 @@
             <xs:element ref="security:hpkp"/>
             <xs:element ref="security:content-security-policy"/>
             <xs:element ref="security:referrer-policy"/>
+            <xs:element ref="security:feature-policy"/>
             <xs:element ref="security:header"/>
          </xs:choice>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
@@ -2464,6 +2465,21 @@
          </xs:simpleType>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="feature-policy">
+      <xs:annotation>
+          <xs:documentation>Adds support for Feature Policy</xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+          <xs:attributeGroup ref="security:feature-options.attlist"/>
+      </xs:complexType>
+  </xs:element>
+  <xs:attributeGroup name="feature-options.attlist">
+      <xs:attribute name="policy-directives" type="xs:token">
+          <xs:annotation>
+              <xs:documentation>The security policy directive(s) for the Feature-Policy header.</xs:documentation>
+          </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
   <xs:element name="cache-control">
       <xs:annotation>
          <xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for
@@ -2719,4 +2735,4 @@
          <xs:enumeration value="LAST"/>
       </xs:restriction>
   </xs:simpleType>
-</xs:schema>
+</xs:schema>

+ 46 - 1
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.springframework.security.config.annotation.web.configurers
 
 import org.springframework.beans.factory.BeanCreationException
@@ -20,14 +21,17 @@ import org.springframework.security.config.annotation.BaseSpringSpec
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+
 import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy
 
 /**
+ * Tests for {@link HeadersConfigurer}.
  *
  * @author Rob Winch
  * @author Tim Ysewyn
  * @author Joe Grandja
  * @author Eddú Meléndez
+ * @author Vedran Pavic
  */
 class HeadersConfigurerTests extends BaseSpringSpec {
 
@@ -497,4 +501,45 @@ class HeadersConfigurerTests extends BaseSpringSpec {
 		}
 	}
 
+	def "headers.featurePolicy default header"() {
+		setup:
+		loadConfig(FeaturePolicyDefaultConfig)
+		request.secure = true
+		when:
+		springSecurityFilterChain.doFilter(request, response, chain)
+		then:
+		responseHeaders == ['Feature-Policy': 'geolocation \'self\'']
+	}
+
+	@EnableWebSecurity
+	static class FeaturePolicyDefaultConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+					.headers()
+					.defaultsDisabled()
+					.featurePolicy("geolocation 'self'");
+		}
+	}
+
+	def "headers.featurePolicy empty policyDirectives"() {
+		when:
+		loadConfig(FeaturePolicyInvalidConfig)
+		then:
+		thrown(BeanCreationException)
+	}
+
+	@EnableWebSecurity
+	static class FeaturePolicyInvalidConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+					.headers()
+					.defaultsDisabled()
+					.featurePolicy("");
+		}
+	}
+
 }

+ 20 - 0
docs/manual/src/docs/asciidoc/_includes/appendix/namespace.adoc

@@ -241,6 +241,7 @@ This allows HTTPS websites to resist impersonation by attackers using mis-issued
 ** `Content-Security-Policy` or `Content-Security-Policy-Report-Only` - Can be set using the <<nsa-content-security-policy,content-security-policy>> element.
 https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS).
 ** `Referrer-Policy` - Can be set using the <<nsa-referrer-policy,referrer-policy>> element, https://www.w3.org/TR/referrer-policy/[Referrer-Policy] is a mechanism that web applications can leverage to manage the referrer field, which contains the last page the user was on.
+** `Feature-Policy` - Can be set using the <<nsa-feature-policy,feature-policy>> element, https://wicg.github.io/feature-policy/[Feature-Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
 
 [[nsa-headers-attributes]]
 ===== <headers> Attributes
@@ -272,6 +273,7 @@ The default is false (the headers are enabled).
 * <<nsa-cache-control,cache-control>>
 * <<nsa-content-security-policy,content-security-policy>>
 * <<nsa-content-type-options,content-type-options>>
+* <<nsa-feature-policy,feature-policy>>
 * <<nsa-frame-options,frame-options>>
 * <<nsa-header,header>>
 * <<nsa-hpkp,hpkp>>
@@ -459,6 +461,24 @@ Default "no-referrer".
 
 
 
+[[nsa-feature-policy]]
+==== <feature-policy>
+When enabled adds the https://wicg.github.io/feature-policy/[Feature Policy] header to the response.
+
+[[nsa-feature-policy-attributes]]
+===== <feature-policy> Attributes
+
+[[nsa-feature-policy-policy-directives]]
+* **policy-directives**
+The security policy directive(s) for the Feature-Policy header.
+
+[[nsa-feature-policy-parents]]
+===== Parent Elements of <feature-policy>
+
+* <<nsa-headers,headers>>
+
+
+
 [[nsa-frame-options]]
 ==== <frame-options>
 When enabled adds the http://tools.ietf.org/html/draft-ietf-websec-x-frame-options[X-Frame-Options header] to the response, this allows newer browsers to do some security checks and prevent http://en.wikipedia.org/wiki/Clickjacking[clickjacking] attacks.

+ 50 - 0
docs/manual/src/docs/asciidoc/_includes/web/headers.adoc

@@ -714,6 +714,56 @@ protected void configure(HttpSecurity http) throws Exception {
 ----
 
 
+[[headers-feature]]
+==== Feature Policy
+
+https://wicg.github.io/feature-policy/[Feature Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
+
+[source]
+----
+Feature-Policy: geolocation 'self'
+----
+
+With Feature Policy, developers can opt-in to a set of "policies" for the browser to enforce on specific features used throughout your site.
+These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
+
+[[headers-feature-configure]]
+===== Configuring Feature Policy
+
+Spring Security *_doesn't add_* Feature Policy header by default.
+
+You can enable the Feature-Policy header using XML configuration with the <<nsa-feature-policy,<feature-policy>>> element as shown below:
+
+[source,xml]
+----
+<http>
+	<!-- ... -->
+
+	<headers>
+		<feature-policy policy-directives="geolocation 'self'" />
+	</headers>
+</http>
+----
+
+Similarly, you can enable the Feature Policy header using Java configuration as shown below:
+
+[source,java]
+----
+@EnableWebSecurity
+public class WebSecurityConfig extends
+WebSecurityConfigurerAdapter {
+
+@Override
+protected void configure(HttpSecurity http) throws Exception {
+	http
+	// ...
+	.headers()
+		.featurePolicy("geolocation 'self'");
+}
+}
+----
+
+
 [[headers-custom]]
 === Custom Headers
 Spring Security has mechanisms to make it convenient to add the more common security headers to your application.

+ 76 - 0
web/src/main/java/org/springframework/security/web/header/writers/FeaturePolicyHeaderWriter.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2018 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
+ *
+ *      http://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.security.web.header.writers;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.web.header.HeaderWriter;
+import org.springframework.util.Assert;
+
+/**
+ * Provides support for <a href="https://wicg.github.io/feature-policy/">Feature
+ * Policy</a>.
+ * <p>
+ * Feature Policy allows web developers to selectively enable, disable, and modify the
+ * behavior of certain APIs and web features in the browser.
+ * <p>
+ * A declaration of a feature policy contains a set of security policy directives, each
+ * responsible for declaring the restrictions for a particular feature type.
+ *
+ * @author Vedran Pavic
+ * @since 5.1
+ */
+public final class FeaturePolicyHeaderWriter implements HeaderWriter {
+
+	private static final String FEATURE_POLICY_HEADER = "Feature-Policy";
+
+	private String policyDirectives;
+
+	/**
+	 * Create a new instance of {@link FeaturePolicyHeaderWriter} with supplied security
+	 * policy directive(s).
+	 *
+	 * @param policyDirectives the security policy directive(s)
+	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
+	 */
+	public FeaturePolicyHeaderWriter(String policyDirectives) {
+		setPolicyDirectives(policyDirectives);
+	}
+
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		response.setHeader(FEATURE_POLICY_HEADER, this.policyDirectives);
+	}
+
+	/**
+	 * Set the security policy directive(s) to be used in the response header.
+	 *
+	 * @param policyDirectives the security policy directive(s)
+	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
+	 */
+	public void setPolicyDirectives(String policyDirectives) {
+		Assert.hasLength(policyDirectives, "policyDirectives must not be null or empty");
+		this.policyDirectives = policyDirectives;
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getName() + " [policyDirectives=" + this.policyDirectives + "]";
+	}
+
+}

+ 73 - 0
web/src/test/java/org/springframework/security/web/header/writers/FeaturePolicyHeaderWriterTests.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2018 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
+ *
+ *      http://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.security.web.header.writers;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link FeaturePolicyHeaderWriter}.
+ *
+ * @author Vedran Pavic
+ */
+public class FeaturePolicyHeaderWriterTests {
+
+	private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation 'self'";
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	private FeaturePolicyHeaderWriter writer;
+
+	@Before
+	public void setUp() {
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+		this.writer = new FeaturePolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersFeaturePolicyDefault() {
+		writer.writeHeaders(this.request, this.response);
+
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeader("Feature-Policy"))
+				.isEqualTo(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void createWriterWithNullDirectivesShouldThrowException() {
+		assertThatThrownBy(() -> new FeaturePolicyHeaderWriter(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("policyDirectives must not be null or empty");
+	}
+
+	@Test
+	public void createWriterWithEmptyDirectivesShouldThrowException() {
+		assertThatThrownBy(() -> new FeaturePolicyHeaderWriter(""))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("policyDirectives must not be null or empty");
+	}
+
+}