KNOX-1017 - Add support for enabling "Strict-Transport-Security" header in Knox responses (Latha Appanna via lmccay)
Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/b60322a6 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/b60322a6 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/b60322a6 Branch: refs/heads/KNOX-998-Package_Restructuring Commit: b60322a6e2ba97b9f989d1efbe5183bfbc009c56 Parents: 710e784 Author: Larry McCay <[email protected]> Authored: Sun Oct 29 16:05:16 2017 -0400 Committer: Larry McCay <[email protected]> Committed: Sun Oct 29 16:05:16 2017 -0400 ---------------------------------------------------------------------- .../webappsec/deploy/WebAppSecContributor.java | 11 ++ .../webappsec/filter/StrictTranportFilter.java | 137 ++++++++++++++++ .../webappsec/StrictTranportFilterTest.java | 164 +++++++++++++++++++ .../home/conf/topologies/manager.xml | 1 + gateway-release/home/templates/sandbox-apps.xml | 1 + 5 files changed, 314 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/b60322a6/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/deploy/WebAppSecContributor.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/deploy/WebAppSecContributor.java b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/deploy/WebAppSecContributor.java index 3904ff1..9a1d174 100644 --- a/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/deploy/WebAppSecContributor.java +++ b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/deploy/WebAppSecContributor.java @@ -42,6 +42,9 @@ public class WebAppSecContributor extends private static final String XFRAME_OPTIONS_SUFFIX = "_XFRAMEOPTIONS"; private static final String XFRAME_OPTIONS_FILTER_CLASSNAME = "org.apache.hadoop.gateway.webappsec.filter.XFrameOptionsFilter"; private static final String XFRAME_OPTIONS_ENABLED = "xframe.options.enabled"; + private static final String STRICT_TRANSPORT_SUFFIX = "_STRICTTRANSPORT"; + private static final String STRICT_TRANSPORT_FILTER_CLASSNAME = "org.apache.hadoop.gateway.webappsec.filter.StrictTranportFilter"; + private static final String STRICT_TRANSPORT_ENABLED = "strict.transport.enabled"; @Override public String getRole() { @@ -92,6 +95,14 @@ public class WebAppSecContributor extends provisionConfig(resource, providerParams, params, "xframe."); resource.addFilter().name( getName() + XFRAME_OPTIONS_SUFFIX ).role( getRole() ).impl( XFRAME_OPTIONS_FILTER_CLASSNAME ).params( params ); } + + // HTTP Strict-Transport-Security + params = new ArrayList<FilterParamDescriptor>(); + String strictTranportEnabled = map.get(STRICT_TRANSPORT_ENABLED); + if ( strictTranportEnabled != null && "true".equals(strictTranportEnabled)) { + provisionConfig(resource, providerParams, params, "strict."); + resource.addFilter().name( getName() + STRICT_TRANSPORT_SUFFIX).role( getRole() ).impl(STRICT_TRANSPORT_FILTER_CLASSNAME).params( params ); + } } } http://git-wip-us.apache.org/repos/asf/knox/blob/b60322a6/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/StrictTranportFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/StrictTranportFilter.java b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/StrictTranportFilter.java new file mode 100644 index 0000000..28ac18a --- /dev/null +++ b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/StrictTranportFilter.java @@ -0,0 +1,137 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.gateway.webappsec.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * This filter protects proxied webapps from protocol downgrade attacks + * and cookie hijacking. + */ +public class StrictTranportFilter implements Filter { + private static final String STRICT_TRANSPORT = "Strict-Transport-Security"; + private static final String CUSTOM_HEADER_PARAM = "strict.transport"; + + private String option = "max-age=31536000"; + + /* (non-Javadoc) + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) throws IOException, ServletException { + ((HttpServletResponse) res).setHeader(STRICT_TRANSPORT, option); + chain.doFilter(req, new StrictTranportResponseWrapper((HttpServletResponse) res)); + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig config) throws ServletException { + String customOption = config.getInitParameter(CUSTOM_HEADER_PARAM); + if (customOption != null) { + option = customOption; + } + } + + public class StrictTranportResponseWrapper extends HttpServletResponseWrapper { + @Override + public void addHeader(String name, String value) { + // don't allow additional values to be added to + // the configured options value in topology + if (!name.equals(STRICT_TRANSPORT)) { + super.addHeader(name, value); + } + } + + @Override + public void setHeader(String name, String value) { + // don't allow overwriting of configured value + if (!name.equals(STRICT_TRANSPORT)) { + super.setHeader(name, value); + } + } + + /** + * construct a wrapper for this request + * + * @param request + */ + public StrictTranportResponseWrapper(HttpServletResponse response) { + super(response); + } + + @Override + public String getHeader(String name) { + String headerValue = null; + if (name.equals(STRICT_TRANSPORT)) { + headerValue = option; + } + else { + headerValue = super.getHeader(name); + } + return headerValue; + } + + /** + * get the Header names + */ + @Override + public Collection<String> getHeaderNames() { + List<String> names = (List<String>) super.getHeaderNames(); + if (names == null) { + names = new ArrayList<String>(); + } + names.add(STRICT_TRANSPORT); + return names; + } + + @Override + public Collection<String> getHeaders(String name) { + List<String> values = (List<String>) super.getHeaders(name); + if (name.equals(STRICT_TRANSPORT)) { + if (values == null) { + values = new ArrayList<String>(); + } + values.add(option); + } + return values; + } + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/b60322a6/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/StrictTranportFilterTest.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/StrictTranportFilterTest.java b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/StrictTranportFilterTest.java new file mode 100644 index 0000000..0c63d7f --- /dev/null +++ b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/StrictTranportFilterTest.java @@ -0,0 +1,164 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.gateway.webappsec; + +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Properties; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.hadoop.gateway.webappsec.filter.StrictTranportFilter; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Test; + +/** + * + */ +public class StrictTranportFilterTest { + /** + * + */ + private static final String STRICT_TRANSPORT = "Strict-Transport-Security"; + String options = null; + Collection<String> headerNames = null; + Collection<String> headers = null; + + @Test + public void testDefaultOptionsValue() throws Exception { + try { + StrictTranportFilter filter = new StrictTranportFilter(); + Properties props = new Properties(); + props.put("strict.transport.enabled", "true"); + filter.init(new TestFilterConfig(props)); + + HttpServletRequest request = EasyMock.createNiceMock( + HttpServletRequest.class); + HttpServletResponse response = EasyMock.createNiceMock( + HttpServletResponse.class); + EasyMock.replay(request); + EasyMock.replay(response); + + TestFilterChain chain = new TestFilterChain(); + filter.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", + chain.doFilterCalled ); + Assert.assertTrue("Options value incorrect should be max-age=31536000 but is: " + + options, "max-age=31536000".equals(options)); + + Assert.assertTrue("Strict-Transport-Security count not equal to 1.", headers.size() == 1); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testConfiguredOptionsValue() throws Exception { + try { + StrictTranportFilter filter = new StrictTranportFilter(); + Properties props = new Properties(); + props.put("strict.transport.enabled", "true"); + props.put("strict.transport", "max-age=31536010; includeSubDomains"); + filter.init(new TestFilterConfig(props)); + + HttpServletRequest request = EasyMock.createNiceMock( + HttpServletRequest.class); + HttpServletResponse response = EasyMock.createNiceMock( + HttpServletResponse.class); + EasyMock.replay(request); + EasyMock.replay(response); + + TestFilterChain chain = new TestFilterChain(); + filter.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", + chain.doFilterCalled ); + Assert.assertTrue("Options value incorrect should be max-age=31536010; includeSubDomains but is: " + + options, "max-age=31536010; includeSubDomains".equals(options)); + + Assert.assertTrue("Strict-Transport-Security count not equal to 1.", headers.size() == 1); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + class TestFilterConfig implements FilterConfig { + Properties props = null; + + public TestFilterConfig(Properties props) { + this.props = props; + } + + @Override + public String getFilterName() { + return null; + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getServletContext() + */ + @Override + public ServletContext getServletContext() { + return null; + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getInitParameter(java.lang.String) + */ + @Override + public String getInitParameter(String name) { + return props.getProperty(name, null); + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getInitParameterNames() + */ + @Override + public Enumeration<String> getInitParameterNames() { + return null; + } + + } + + class TestFilterChain implements FilterChain { + boolean doFilterCalled = false; + + /* (non-Javadoc) + * @see javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + doFilterCalled = true; + options = ((HttpServletResponse)response).getHeader(STRICT_TRANSPORT); + headerNames = ((HttpServletResponse)response).getHeaderNames(); + headers = ((HttpServletResponse)response).getHeaders(STRICT_TRANSPORT); + } + + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/b60322a6/gateway-release/home/conf/topologies/manager.xml ---------------------------------------------------------------------- diff --git a/gateway-release/home/conf/topologies/manager.xml b/gateway-release/home/conf/topologies/manager.xml index 22f8a91..08416c3 100644 --- a/gateway-release/home/conf/topologies/manager.xml +++ b/gateway-release/home/conf/topologies/manager.xml @@ -27,6 +27,7 @@ <param><name>csrf.customHeader</name><value>X-XSRF-Header</value></param> <param><name>csrf.methodsToIgnore</name><value>GET,OPTIONS,HEAD</value></param> <param><name>xframe.options.enabled</name><value>true</value></param> + <param><name>strict.transport.enabled</name><value>true</value></param> </provider> <provider> http://git-wip-us.apache.org/repos/asf/knox/blob/b60322a6/gateway-release/home/templates/sandbox-apps.xml ---------------------------------------------------------------------- diff --git a/gateway-release/home/templates/sandbox-apps.xml b/gateway-release/home/templates/sandbox-apps.xml index bed6470..44cb5f2 100644 --- a/gateway-release/home/templates/sandbox-apps.xml +++ b/gateway-release/home/templates/sandbox-apps.xml @@ -22,6 +22,7 @@ <enabled>true</enabled> <param><name>xframe.options.enabled</name><value>true</value></param> <param><name>csrf.enabled</name><value>true</value></param> + <param><name>strict.transport.enabled</name><value>true</value></param> </provider> <gateway>
