You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2016/03/29 05:02:41 UTC

knox git commit: KNOX-700 - Add Clickjacking Protection to WebAppSec Provider

Repository: knox
Updated Branches:
  refs/heads/master 61b4f8d24 -> 69cbfa587


KNOX-700 - Add Clickjacking Protection to WebAppSec Provider

Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/69cbfa58
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/69cbfa58
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/69cbfa58

Branch: refs/heads/master
Commit: 69cbfa58705def7f80e0a31e05924505f8b2811e
Parents: 61b4f8d
Author: Larry McCay <lm...@hortonworks.com>
Authored: Mon Mar 28 23:02:30 2016 -0400
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Mon Mar 28 23:02:30 2016 -0400

----------------------------------------------------------------------
 .../webappsec/deploy/WebAppSecContributor.java  |  30 ++-
 .../webappsec/filter/XFrameOptionsFilter.java   | 137 +++++++++++++
 .../gateway/provider/federation/CSRFTest.java   |  30 ---
 .../hadoop/gateway/webappsec/CSRFTest.java      |  29 +++
 .../webappsec/XFrameOptionsFilterTest.java      | 193 +++++++++++++++++++
 5 files changed, 383 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/69cbfa58/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 86cf899..50a6767 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
@@ -39,6 +39,9 @@ public class WebAppSecContributor extends
   private static final String CORS_SUFFIX = "_CORS";
   private static final String CORS_FILTER_CLASSNAME = "com.thetransactioncompany.cors.CORSFilter";
   private static final String CORS_ENABLED = "cors.enabled";
+  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";
 
   @Override
   public String getRole() {
@@ -62,27 +65,42 @@ public class WebAppSecContributor extends
     Provider webappsec = context.getTopology().getProvider(ROLE, NAME);
     if (webappsec != null && webappsec.isEnabled()) {
       Map<String,String> map = provider.getParams();
-      String csrfEnabled = map.get(CSRF_ENABLED);
       if (params == null) {
         params = new ArrayList<FilterParamDescriptor>();
       }
-      // blindly add all the provider params as filter init params
-      Map<String, String> providerParams = provider.getParams();
-      for(Entry<String, String> entry : providerParams.entrySet()) {
-        params.add( resource.createFilterParam().name( entry.getKey().toLowerCase() ).value( entry.getValue() ) );
-      }
 
+      Map<String, String> providerParams = provider.getParams();
       // CORS support
       String corsEnabled = map.get(CORS_ENABLED);
       if ( corsEnabled != null && corsEnabled.equals("true")) {
+        provisionConfig(resource, providerParams, params, "cors.");
         resource.addFilter().name( getName() + CORS_SUFFIX ).role( getRole() ).impl( CORS_FILTER_CLASSNAME ).params( params );
       }
 
       // CRSF
+      params = new ArrayList<FilterParamDescriptor>();
+      String csrfEnabled = map.get(CSRF_ENABLED);
       if ( csrfEnabled != null && csrfEnabled.equals("true")) {
+        provisionConfig(resource, providerParams, params, "csrf.");
         resource.addFilter().name( getName() + CSRF_SUFFIX ).role( getRole() ).impl( CSRF_FILTER_CLASSNAME ).params( params );
       }
 
+      // X-Frame-Options - clickjacking protection
+      params = new ArrayList<FilterParamDescriptor>();
+      String xframeOptionsEnabled = map.get(XFRAME_OPTIONS_ENABLED);
+      if ( xframeOptionsEnabled != null && xframeOptionsEnabled.equals("true")) {
+        provisionConfig(resource, providerParams, params, "xframe.");
+        resource.addFilter().name( getName() + XFRAME_OPTIONS_SUFFIX ).role( getRole() ).impl( XFRAME_OPTIONS_FILTER_CLASSNAME ).params( params );
+      }
+    }
+  }
+
+  private void provisionConfig(ResourceDescriptor resource, Map<String,String> providerParams,
+      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() ) );
+      }
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/69cbfa58/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/XFrameOptionsFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/XFrameOptionsFilter.java b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/XFrameOptionsFilter.java
new file mode 100644
index 0000000..9ec14a6
--- /dev/null
+++ b/gateway-provider-security-webappsec/src/main/java/org/apache/hadoop/gateway/webappsec/filter/XFrameOptionsFilter.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 clickjacking attacks that
+ * are possible through use of Frames to contain the proxied resources.
+ */
+public class XFrameOptionsFilter implements Filter {
+  private static final String X_FRAME_OPTIONS = "X-Frame-Options";
+  private static final String CUSTOM_HEADER_PARAM = "xframe.options";
+
+  private String option = "DENY";
+
+  /* (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(X_FRAME_OPTIONS, option);
+    chain.doFilter(req, new XFrameOptionsResponseWrapper((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 XFrameOptionsResponseWrapper 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(X_FRAME_OPTIONS)) {
+        super.addHeader(name, value);
+      }
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+      // don't allow overwriting of configured value
+      if (!name.equals(X_FRAME_OPTIONS)) {
+        super.setHeader(name, value);
+      }
+    }
+
+    /**
+     * construct a wrapper for this request
+     * 
+     * @param request
+     */
+    public XFrameOptionsResponseWrapper(HttpServletResponse response) {
+        super(response);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        String headerValue = null;
+        if (name.equals(X_FRAME_OPTIONS)) {
+            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(X_FRAME_OPTIONS);
+        return names;
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+        List<String> values = (List<String>) super.getHeaders(name);
+        if (name.equals(X_FRAME_OPTIONS)) {
+          if (values == null) {
+            values = new ArrayList<String>();
+          }
+          values.add(option);
+        }
+        return values;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/69cbfa58/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/provider/federation/CSRFTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/provider/federation/CSRFTest.java b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/provider/federation/CSRFTest.java
deleted file mode 100644
index 28ec021..0000000
--- a/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/provider/federation/CSRFTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * 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.provider.federation;
-
-import junit.framework.TestCase;
-
-import org.apache.hadoop.gateway.services.security.token.impl.JWTToken;
-import org.junit.Test;
-
-public class CSRFTest extends TestCase {
-  @Test
-  public void testCsrf() throws Exception {
-    assertTrue(true);
-  }
-}

http://git-wip-us.apache.org/repos/asf/knox/blob/69cbfa58/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/CSRFTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/CSRFTest.java b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/CSRFTest.java
new file mode 100644
index 0000000..baab4b2
--- /dev/null
+++ b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/CSRFTest.java
@@ -0,0 +1,29 @@
+/**
+ * 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 junit.framework.TestCase;
+
+import org.junit.Test;
+
+public class CSRFTest extends TestCase {
+  @Test
+  public void testCsrf() throws Exception {
+    assertTrue(true);
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/69cbfa58/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/XFrameOptionsFilterTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/XFrameOptionsFilterTest.java b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/XFrameOptionsFilterTest.java
new file mode 100644
index 0000000..cda73f6
--- /dev/null
+++ b/gateway-provider-security-webappsec/src/test/java/org/apache/hadoop/gateway/webappsec/XFrameOptionsFilterTest.java
@@ -0,0 +1,193 @@
+/**
+ * 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.XFrameOptionsFilter;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class XFrameOptionsFilterTest {
+  /**
+   * 
+   */
+  private static final String X_FRAME_OPTIONS = "X-Frame-Options";
+  String options = null;
+  Collection<String> headerNames = null;
+  Collection<String> headers = null;
+
+  @Test
+  public void testDefaultOptionsValue() throws Exception {
+    try {
+      XFrameOptionsFilter filter = new XFrameOptionsFilter();
+      Properties props = new Properties();
+      props.put("xframe.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 == true);
+      Assert.assertTrue("Options value incorrect should be DENY but is: "
+          + options, options.equals("DENY"));
+
+      Assert.assertTrue("X-Frame-Options 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 {
+      XFrameOptionsFilter filter = new XFrameOptionsFilter();
+      Properties props = new Properties();
+      props.put("xframe.options.enabled", "true");
+      props.put("xframe.options", "SAMEORIGIN");
+      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 == true);
+      Assert.assertTrue("Options value incorrect should be SAMEORIGIN but is: "
+          + options, options.equals("SAMEORIGIN"));
+
+      Assert.assertTrue("X-Frame-Options count not equal to 1.", headers.size() == 1);
+    } catch (ServletException se) {
+      fail("Should NOT have thrown a ServletException.");
+    }
+  }
+
+//  @Test
+//  public void testExistingXFrameOptionHeader() throws Exception {
+//    try {
+//      XFrameOptionsFilter filter = new XFrameOptionsFilter();
+//      Properties props = new Properties();
+//      props.put("xframe.options.enabled", "true");
+//      props.put("xframe.options", "SAMEORIGIN");
+//      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 == true);
+//      Assert.assertTrue("Options value incorrect should be SAMEORIGIN but is: "
+//          + options, options.equals("SAMEORIGIN"));
+//
+//      Assert.assertTrue("X-Frame-Options 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(X_FRAME_OPTIONS);
+      headerNames = ((HttpServletResponse)response).getHeaderNames();
+      headers = ((HttpServletResponse)response).getHeaders(X_FRAME_OPTIONS);
+    }
+    
+  }
+
+}