knox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pzamp...@apache.org
Subject [2/5] knox git commit: KNOX-1310 - The X-Content-Type-Options header should be set as 'nosniff'
Date Wed, 16 May 2018 02:15:33 GMT
http://git-wip-us.apache.org/repos/asf/knox/blob/7953de69/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java
b/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java
index eed8ec3..ed5e98d 100644
--- a/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java
+++ b/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java
@@ -29,8 +29,7 @@ import org.apache.knox.gateway.descriptor.ResourceDescriptor;
 import org.apache.knox.gateway.topology.Provider;
 import org.apache.knox.gateway.topology.Service;
 
-public class WebAppSecContributor extends
-    ProviderDeploymentContributorBase {
+public class WebAppSecContributor extends ProviderDeploymentContributorBase {
   private static final String ROLE = "webappsec";
   private static final String NAME = "WebAppSec";
   private static final String CSRF_SUFFIX = "_CSRF";
@@ -42,6 +41,9 @@ public class WebAppSecContributor extends
   private static final String XFRAME_OPTIONS_SUFFIX = "_XFRAMEOPTIONS";
   private static final String XFRAME_OPTIONS_FILTER_CLASSNAME = "org.apache.knox.gateway.webappsec.filter.XFrameOptionsFilter";
   private static final String XFRAME_OPTIONS_ENABLED = "xframe.options.enabled";
+  private static final String XCONTENT_TYPE_OPTIONS_SUFFIX = "_XCONTENTTYPEOPTIONS";
+  private static final String XCONTENT_TYPE_OPTIONS_FILTER_CLASSNAME = "org.apache.knox.gateway.webappsec.filter.XContentTypeOptionsFilter";
+  private static final String XCONTENT_TYPE_OPTIONS_ENABLED = "xcontent-type.options.enabled";
   private static final String XSS_PROTECTION_SUFFIX = "_XSSPROTECTION";
   private static final String XSS_PROTECTION_FILTER_CLASSNAME = "org.apache.knox.gateway.webappsec.filter.XSSProtectionFilter";
   private static final String XSS_PROTECTION_ENABLED = "xss.protection.enabled";
@@ -66,54 +68,83 @@ public class WebAppSecContributor extends
   }
 
   @Override
-  public void contributeFilter(DeploymentContext context, Provider provider, Service service,

-      ResourceDescriptor resource, List<FilterParamDescriptor> params) {
+  public void contributeFilter(DeploymentContext           context,
+                               Provider                    provider,
+                               Service                     service,
+                               ResourceDescriptor          resource,
+                               List<FilterParamDescriptor> params) {
     
     Provider webappsec = context.getTopology().getProvider(ROLE, NAME);
     if (webappsec != null && webappsec.isEnabled()) {
       Map<String,String> map = provider.getParams();
       if (params == null) {
-        params = new ArrayList<FilterParamDescriptor>();
+        params = new ArrayList<>();
       }
 
       Map<String, String> providerParams = provider.getParams();
       // CORS support
       String corsEnabled = map.get(CORS_ENABLED);
-      if ( corsEnabled != null && "true".equals(corsEnabled)) {
+      if (corsEnabled != null && "true".equals(corsEnabled)) {
         provisionConfig(resource, providerParams, params, "cors.");
-        resource.addFilter().name( getName() + CORS_SUFFIX ).role( getRole() ).impl( CORS_FILTER_CLASSNAME
).params( params );
+        resource.addFilter().name(getName() + CORS_SUFFIX)
+                            .role(getRole())
+                            .impl(CORS_FILTER_CLASSNAME)
+                            .params(params);
       }
 
       // CRSF
-      params = new ArrayList<FilterParamDescriptor>();
+      params = new ArrayList<>();
       String csrfEnabled = map.get(CSRF_ENABLED);
-      if ( csrfEnabled != null && "true".equals(csrfEnabled)) {
+      if (csrfEnabled != null && "true".equals(csrfEnabled)) {
         provisionConfig(resource, providerParams, params, "csrf.");
-        resource.addFilter().name( getName() + CSRF_SUFFIX ).role( getRole() ).impl( CSRF_FILTER_CLASSNAME
).params( params );
+        resource.addFilter().name(getName() + CSRF_SUFFIX)
+                            .role(getRole())
+                            .impl(CSRF_FILTER_CLASSNAME)
+                            .params(params);
       }
 
       // X-Frame-Options - clickjacking protection
-      params = new ArrayList<FilterParamDescriptor>();
+      params = new ArrayList<>();
       String xframeOptionsEnabled = map.get(XFRAME_OPTIONS_ENABLED);
-      if ( xframeOptionsEnabled != null && "true".equals(xframeOptionsEnabled)) {
+      if (xframeOptionsEnabled != null && "true".equals(xframeOptionsEnabled)) {
         provisionConfig(resource, providerParams, params, "xframe.");
-        resource.addFilter().name( getName() + XFRAME_OPTIONS_SUFFIX ).role( getRole() ).impl(
XFRAME_OPTIONS_FILTER_CLASSNAME ).params( params );
+        resource.addFilter().name(getName() + XFRAME_OPTIONS_SUFFIX)
+                            .role(getRole())
+                            .impl(XFRAME_OPTIONS_FILTER_CLASSNAME)
+                            .params(params);
+      }
+
+      // X-Content-Type-Options - MIME type sniffing protection
+      params = new ArrayList<>();
+      String xContentTypeOptionsEnabled = map.get(XCONTENT_TYPE_OPTIONS_ENABLED);
+      if (xContentTypeOptionsEnabled != null && "true".equals(xContentTypeOptionsEnabled))
{
+        provisionConfig(resource, providerParams, params, "xcontent-type.");
+        resource.addFilter().name(getName() + XCONTENT_TYPE_OPTIONS_SUFFIX)
+                            .role(getRole())
+                            .impl(XCONTENT_TYPE_OPTIONS_FILTER_CLASSNAME)
+                            .params(params);
       }
 
       // X-XSS-Protection - browser xss protection
-      params = new ArrayList<FilterParamDescriptor>();
+      params = new ArrayList<>();
       String xssProtectionEnabled = map.get(XSS_PROTECTION_ENABLED);
-      if ( xssProtectionEnabled != null && "true".equals(xssProtectionEnabled)) {
+      if (xssProtectionEnabled != null && "true".equals(xssProtectionEnabled)) {
         provisionConfig(resource, providerParams, params, "xss.");
-        resource.addFilter().name( getName() + XSS_PROTECTION_SUFFIX ).role( getRole() ).impl(
XSS_PROTECTION_FILTER_CLASSNAME ).params( params );
+        resource.addFilter().name(getName() + XSS_PROTECTION_SUFFIX)
+                            .role(getRole())
+                            .impl(XSS_PROTECTION_FILTER_CLASSNAME)
+                            .params(params);
       }
 
       // HTTP Strict-Transport-Security
-      params = new ArrayList<FilterParamDescriptor>();
+      params = new ArrayList<>();
       String strictTranportEnabled = map.get(STRICT_TRANSPORT_ENABLED);
-      if ( strictTranportEnabled != null && "true".equals(strictTranportEnabled))
{
+      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 );
+        resource.addFilter().name(getName() + STRICT_TRANSPORT_SUFFIX)
+                            .role(getRole())
+                            .impl(STRICT_TRANSPORT_FILTER_CLASSNAME)
+                            .params(params);
       }
     }
   }
@@ -122,7 +153,7 @@ public class WebAppSecContributor extends
       List<FilterParamDescriptor> params, String prefix) {
     for(Entry<String, String> entry : providerParams.entrySet()) {
       if (entry.getKey().startsWith(prefix)) {
-        params.add( resource.createFilterParam().name( entry.getKey().toLowerCase() ).value(
entry.getValue() ) );
+        params.add(resource.createFilterParam().name(entry.getKey().toLowerCase()).value(entry.getValue()));
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/knox/blob/7953de69/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/filter/XContentTypeOptionsFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/filter/XContentTypeOptionsFilter.java
b/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/filter/XContentTypeOptionsFilter.java
new file mode 100644
index 0000000..b05b335
--- /dev/null
+++ b/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/filter/XContentTypeOptionsFilter.java
@@ -0,0 +1,123 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.webappsec.filter;
+
+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;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class XContentTypeOptionsFilter implements Filter {
+
+  public static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options";
+
+  public static final String CUSTOM_HEADER_PARAM = "xcontent-type.options";
+
+  public static final String DEFAULT_OPTION_VALUE = "nosniff";
+
+  private String option = DEFAULT_OPTION_VALUE;
+
+
+  @Override
+  public void init(FilterConfig config) throws ServletException {
+    String customOption = config.getInitParameter(CUSTOM_HEADER_PARAM);
+    if (customOption != null) {
+      option = customOption;
+    }
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    ((HttpServletResponse) response).setHeader(X_CONTENT_TYPE_OPTIONS_HEADER, option);
+    chain.doFilter(request, new XContentTypeOptionsResponseWrapper((HttpServletResponse)
response));
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+
+  class XContentTypeOptionsResponseWrapper extends HttpServletResponseWrapper {
+
+    XContentTypeOptionsResponseWrapper(HttpServletResponse res) {
+      super(res);
+    }
+
+    @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(X_CONTENT_TYPE_OPTIONS_HEADER)) {
+        super.addHeader(name, value);
+      }
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+      // don't allow overwriting of configured value
+      if (!name.equals(X_CONTENT_TYPE_OPTIONS_HEADER)) {
+        super.setHeader(name, value);
+      }
+    }
+
+    @Override
+    public String getHeader(String name) {
+      String headerValue = null;
+      if (name.equals(X_CONTENT_TYPE_OPTIONS_HEADER)) {
+        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<>();
+      }
+      names.add(X_CONTENT_TYPE_OPTIONS_HEADER);
+      return names;
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+      List<String> values = (List<String>) super.getHeaders(name);
+      if (name.equals(X_CONTENT_TYPE_OPTIONS_HEADER)) {
+        if (values == null) {
+          values = new ArrayList<>();
+        }
+        values.add(option);
+      }
+      return values;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/7953de69/gateway-provider-security-webappsec/src/test/java/org/apache/knox/gateway/webappsec/XContentTypeOptionsFilterTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/test/java/org/apache/knox/gateway/webappsec/XContentTypeOptionsFilterTest.java
b/gateway-provider-security-webappsec/src/test/java/org/apache/knox/gateway/webappsec/XContentTypeOptionsFilterTest.java
new file mode 100644
index 0000000..e5fb587
--- /dev/null
+++ b/gateway-provider-security-webappsec/src/test/java/org/apache/knox/gateway/webappsec/XContentTypeOptionsFilterTest.java
@@ -0,0 +1,141 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.webappsec;
+
+import org.apache.knox.gateway.webappsec.filter.XContentTypeOptionsFilter;
+
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+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 java.util.Collection;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import static org.junit.Assert.fail;
+
+public class XContentTypeOptionsFilterTest {
+
+  private String options = null;
+  private Collection<String> headers = null;
+
+  @Test
+  public void testDefaultOptionsValue() throws Exception {
+    try {
+      final String expectedDefaultValue = XContentTypeOptionsFilter.DEFAULT_OPTION_VALUE;
+
+      XContentTypeOptionsFilter filter = new XContentTypeOptionsFilter();
+      Properties props = new Properties();
+      props.put("xcontent-type.options.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.assertEquals(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER + " value
incorrect.",
+                          expectedDefaultValue, options);
+      Assert.assertEquals(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER + " count
incorrect.",
+                          1, headers.size());
+    } catch (ServletException se) {
+      fail("Should NOT have thrown a ServletException.");
+    }
+  }
+
+  @Test
+  public void testConfiguredOptionsValue() throws Exception {
+    try {
+      final String customOption = "myContentTypeOpts";
+
+      XContentTypeOptionsFilter filter = new XContentTypeOptionsFilter();
+      Properties props = new Properties();
+      props.put("xcontent-type.options.enabled", "true");
+      props.put("xcontent-type.options", customOption);
+      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.assertEquals(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER + " value
incorrect",
+                          customOption, options);
+      Assert.assertEquals(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER + " count
incorrect.",
+                          1, headers.size());
+    } catch (ServletException se) {
+      fail("Should NOT have thrown a ServletException.");
+    }
+  }
+
+  private static class TestFilterConfig implements FilterConfig {
+    Properties props;
+
+    TestFilterConfig(Properties props) {
+      this.props = props;
+    }
+
+    @Override
+    public String getFilterName() {
+      return null;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+      return null;
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+      return props.getProperty(name, null);
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+      return null;
+    }
+
+  }
+
+  class TestFilterChain implements FilterChain {
+    boolean doFilterCalled = false;
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response) {
+      doFilterCalled = true;
+      options = ((HttpServletResponse)response).getHeader(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER);
+      headers = ((HttpServletResponse)response).getHeaders(XContentTypeOptionsFilter.X_CONTENT_TYPE_OPTIONS_HEADER);
+    }
+
+  }
+
+
+}


Mime
View raw message