falcon-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sowmya...@apache.org
Subject falcon git commit: FALCON-2082 Add CSRF filter for REST APIs
Date Fri, 12 Aug 2016 18:05:46 GMT
Repository: falcon
Updated Branches:
  refs/heads/master 9bed024d5 -> c57c8f448


FALCON-2082 Add CSRF filter for REST APIs

Provide the user an option to enable CSRF filter and configure custom header and browser agents.
Forked RestCsrfPreventionFilter from Hadoop 2.8.0 and fixed checkstyle errors. Tested with
CSRF filter enabled and custom header "FALCON-CSRF-FILTER" that 1) GET methods always work;
2) POST/DELETE methods will be accepted only if the user provides the correct custom header
(See picture below); 3) Falcon CLI works well.

<img width="1073" alt="screen shot 2016-07-27 at 4 17 32 pm" src="https://cloud.githubusercontent.com/assets/10202347/17198598/7a4daf30-542b-11e6-8b47-65bec46f901e.png">

<img width="1080" alt="screen shot 2016-07-27 at 4 17 07 pm" src="https://cloud.githubusercontent.com/assets/10202347/17198606/8bb10682-542b-11e6-8964-0f5faeb6f60a.png">

Author: yzheng-hortonworks <yzheng@hortonworks.com>

Reviewers: "Venkat Ranganathan<vrangan@apache.org>, Sowmya Ramesh <sowmya_kr@apache.org>"

Closes #238 from yzheng-hortonworks/FALCON-2082


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

Branch: refs/heads/master
Commit: c57c8f448821e1c2ab9a9ebd4b4fd04dfcaab3cc
Parents: 9bed024
Author: yzheng-hortonworks <yzheng@hortonworks.com>
Authored: Fri Aug 12 11:05:42 2016 -0700
Committer: Sowmya Ramesh <sramesh@hortonworks.com>
Committed: Fri Aug 12 11:05:42 2016 -0700

----------------------------------------------------------------------
 .../apache/falcon/security/SecurityUtil.java    |  10 +
 common/src/main/resources/startup.properties    |   9 +
 .../falcon/security/SecurityUtilTest.java       |  16 ++
 docs/src/site/twiki/Security.twiki              |  26 ++-
 .../falcon/security/FalconCSRFFilter.java       |  81 ++++++++
 .../security/RestCsrfPreventionFilter.java      | 189 +++++++++++++++++++
 prism/src/main/webapp/WEB-INF/web.xml           |  14 ++
 .../falcon/security/FalconCSRFFilterTest.java   | 138 ++++++++++++++
 src/conf/startup.properties                     |   9 +
 .../src/main/webapp/WEB-INF/distributed/web.xml |  14 ++
 webapp/src/main/webapp/WEB-INF/embedded/web.xml |  14 ++
 webapp/src/main/webapp/WEB-INF/web.xml          |  14 ++
 12 files changed, 529 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/common/src/main/java/org/apache/falcon/security/SecurityUtil.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/falcon/security/SecurityUtil.java b/common/src/main/java/org/apache/falcon/security/SecurityUtil.java
index 7191f72..0f1d1c8 100644
--- a/common/src/main/java/org/apache/falcon/security/SecurityUtil.java
+++ b/common/src/main/java/org/apache/falcon/security/SecurityUtil.java
@@ -115,6 +115,16 @@ public final class SecurityUtil {
                 "falcon.security.authorization.enabled", "false"));
     }
 
+    /**
+     * Checks if CSRF filter is enabled in the configuration.
+     *
+     * @return true if falcon.security.csrf.enabled is enabled, false otherwise
+     */
+    public static boolean isCSRFFilterEnabled() {
+        return Boolean.valueOf(StartupProperties.get().getProperty(
+                "falcon.security.csrf.enabled", "false"));
+    }
+
     public static AuthorizationProvider getAuthorizationProvider() throws FalconException
{
         String providerClassName = StartupProperties.get().getProperty(
                 "falcon.security.authorization.provider",

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/common/src/main/resources/startup.properties
----------------------------------------------------------------------
diff --git a/common/src/main/resources/startup.properties b/common/src/main/resources/startup.properties
index 9207b25..6c2ab5c 100644
--- a/common/src/main/resources/startup.properties
+++ b/common/src/main/resources/startup.properties
@@ -244,6 +244,15 @@ it.workflow.execution.listeners=org.apache.falcon.catalog.CatalogPartitionHandle
 # Authorization Enabled flag: false (default)|true
 *.falcon.security.authorization.enabled=false
 
+# CSRF filter enabled flag: false (default) | true
+*.falcon.security.csrf.enabled=false
+
+# Custom header for CSRF filter
+*.falcon.security.csrf.header=FALCON-CSRF-FILTER
+
+# Browser user agents to be filtered
+*.falcon.security.csrf.browser=^Mozilla.*,^Opera.*
+
 # The name of the group of super-users
 *.falcon.security.authorization.superusergroup=falcon
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/common/src/test/java/org/apache/falcon/security/SecurityUtilTest.java
----------------------------------------------------------------------
diff --git a/common/src/test/java/org/apache/falcon/security/SecurityUtilTest.java b/common/src/test/java/org/apache/falcon/security/SecurityUtilTest.java
index d47acbc..45834d1 100644
--- a/common/src/test/java/org/apache/falcon/security/SecurityUtilTest.java
+++ b/common/src/test/java/org/apache/falcon/security/SecurityUtilTest.java
@@ -120,6 +120,22 @@ public class SecurityUtilTest {
     }
 
     @Test
+    public void testIsCSRFFilterEnabledByDefault() throws Exception {
+        Assert.assertFalse(SecurityUtil.isCSRFFilterEnabled());
+    }
+
+    @Test
+    public void testIsCSRFFilterEnabled() throws Exception {
+        try {
+            StartupProperties.get().setProperty("falcon.security.csrf.enabled", "true");
+            Assert.assertTrue(SecurityUtil.isCSRFFilterEnabled());
+        } finally {
+            // reset
+            StartupProperties.get().setProperty("falcon.security.csrf.enabled", "false");
+        }
+    }
+
+    @Test
     public void testTryProxy() throws IOException, FalconException {
         Process process = Mockito.mock(Process.class);
         StartupProperties.get().setProperty("falcon.security.authorization.enabled", "true");

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/docs/src/site/twiki/Security.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/Security.twiki b/docs/src/site/twiki/Security.twiki
index b17650c..25a9bb5 100644
--- a/docs/src/site/twiki/Security.twiki
+++ b/docs/src/site/twiki/Security.twiki
@@ -3,11 +3,11 @@
 ---++ Overview
 
 Apache Falcon provides the following security features:
-   * Support credential provider alias for passwords used in Falcon server.
-   * Support authentication to identify proper users.
-   * Support authorization to specify resource access permission for users or groups.
-   * Support SSL to provide transport level security for data confidentiality and integrity.
-
+   * Credential provider alias for passwords used in Falcon server.
+   * Authentication to identify proper users.
+   * Authorization to specify resource access permission for users or groups.
+   * Cross-Site Request Forgery (CSRF) prevention.
+   * SSL to provide transport level security for data confidentiality and integrity.
 
 ---++ Credential Provider Alias for Passwords
 Server-side configuration properties (i.e. startup.properties) contain passwords and other
sensitive information.
@@ -310,6 +310,22 @@ Administrative groups are determined by the configuration:
 *.falcon.security.authorization.admin.groups=falcon,testgroup,staff
 </verbatim>
 
+---++ Cross-Site Request Forgery (CSRF) Prevention
+
+Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted
actions on a web application in which they're currently authenticated.
+Falcon provides an option to prevent CSRF with Hadoop CSRF filter for REST APIs. By default,
Falcon CSRF filter is disabled.
+To enable the support for CSRF prevention, set falcon.security.csrf.enabled to true in the
startup configuration.
+We also provide options to configure custom header and browser user agents.
+
+<verbatim>
+# CSRF filter enabled flag: false (default) | true
+*.falcon.security.csrf.enabled=true
+# Custom header for CSRF filter
+*.falcon.security.csrf.header=FALCON-CSRF-FILTER
+# Browser user agents to be filtered
+*.falcon.security.csrf.browser=^Mozilla.*,^Opera.*
+</verbatim>
+
 
 ---++ SSL
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/prism/src/main/java/org/apache/falcon/security/FalconCSRFFilter.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/security/FalconCSRFFilter.java b/prism/src/main/java/org/apache/falcon/security/FalconCSRFFilter.java
new file mode 100644
index 0000000..f3dcf63
--- /dev/null
+++ b/prism/src/main/java/org/apache/falcon/security/FalconCSRFFilter.java
@@ -0,0 +1,81 @@
+/**
+ * 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.falcon.security;
+
+import org.apache.falcon.util.StartupProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * CSRF filter before processing the request.
+ */
+public class FalconCSRFFilter extends RestCsrfPreventionFilter {
+    private static final Logger LOG = LoggerFactory.getLogger(FalconCSRFFilter.class);
+
+    public static final String CSRF_PROP_KEY_PREFIX = "falcon.security.csrf.";
+    public static final String CSRF_PROP_KEY_CUSTOMER_HEADER = "header";
+    public static final String CSRF_PROP_KEY_BROWSER_USER_AGENT = "browser";
+
+    private boolean isCSRFFilterEnabled;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        isCSRFFilterEnabled = SecurityUtil.isCSRFFilterEnabled();
+        if (isCSRFFilterEnabled) {
+            super.init(filterConfig);
+
+            // add additional property: custom header
+            Properties configProperties = StartupProperties.get();
+            String customHeader = configProperties.getProperty(CSRF_PROP_KEY_PREFIX + CSRF_PROP_KEY_CUSTOMER_HEADER);
+            if (customHeader != null) {
+                super.headerName = customHeader;
+            }
+
+            // add additional property: browser user agent
+            String browerAgents = configProperties.getProperty(CSRF_PROP_KEY_PREFIX + CSRF_PROP_KEY_BROWSER_USER_AGENT);
+            if (browerAgents != null) {
+                super.parseBrowserUserAgents(browerAgents);
+            }
+
+            LOG.info("Adding cross-site request forgery (CSRF) protection, headerName = {},"
+                            + "methodsToIgnore = {}, " + "browserUserAgents = {}",
+                    new Object[]{super.headerName, super.methodsToIgnore, super.browserUserAgents});
+        } else {
+            LOG.info("CSRF filter is not enabled.");
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+                         final FilterChain filterChain) throws IOException, ServletException
{
+        if (isCSRFFilterEnabled) {
+            super.doFilter(request, response, filterChain);
+        } else {
+            filterChain.doFilter(request, response);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/prism/src/main/java/org/apache/falcon/security/RestCsrfPreventionFilter.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/security/RestCsrfPreventionFilter.java
b/prism/src/main/java/org/apache/falcon/security/RestCsrfPreventionFilter.java
new file mode 100644
index 0000000..a9cfb0f
--- /dev/null
+++ b/prism/src/main/java/org/apache/falcon/security/RestCsrfPreventionFilter.java
@@ -0,0 +1,189 @@
+/**
+ * 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.falcon.security;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.hadoop.classification.InterfaceAudience.Public;
+import org.apache.hadoop.classification.InterfaceStability.Evolving;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Source code forked from Hadoop 2.8.0+ org.apache.hadoop.security.http.RestCsrfPreventionFilter.
+ */
+@Public
+@Evolving
+public class RestCsrfPreventionFilter implements Filter {
+    private static final Logger LOG = LoggerFactory.getLogger(RestCsrfPreventionFilter.class);
+    public static final String HEADER_USER_AGENT = "User-Agent";
+    public static final String BROWSER_USER_AGENT_PARAM = "browser-useragents-regex";
+    public static final String CUSTOM_HEADER_PARAM = "custom-header";
+    public static final String CUSTOM_METHODS_TO_IGNORE_PARAM = "methods-to-ignore";
+    static final String BROWSER_USER_AGENTS_DEFAULT = "^Mozilla.*,^Opera.*";
+    public static final String HEADER_DEFAULT = "X-XSRF-HEADER";
+    static final String METHODS_TO_IGNORE_DEFAULT = "GET,OPTIONS,HEAD,TRACE";
+    public static final String CSRF_ERROR_MESSAGE = "Missing Required Header for CSRF Vulnerability
Protection";
+    protected String headerName = "X-XSRF-HEADER";
+    protected Set<String> methodsToIgnore = null;
+    protected Set<Pattern> browserUserAgents;
+
+    public RestCsrfPreventionFilter() {
+    }
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+        String customHeader = filterConfig.getInitParameter(CUSTOM_HEADER_PARAM);
+        if (customHeader != null) {
+            this.headerName = customHeader;
+        }
+
+        String customMethodsToIgnore = filterConfig.getInitParameter(CUSTOM_METHODS_TO_IGNORE_PARAM);
+        if (customMethodsToIgnore != null) {
+            this.parseMethodsToIgnore(customMethodsToIgnore);
+        } else {
+            this.parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT);
+        }
+
+        String agents = filterConfig.getInitParameter(BROWSER_USER_AGENT_PARAM);
+        if (agents == null) {
+            agents = BROWSER_USER_AGENTS_DEFAULT;
+        }
+
+        this.parseBrowserUserAgents(agents);
+    }
+
+    void parseBrowserUserAgents(String userAgents) {
+        String[] agentsArray = userAgents.split(",");
+        this.browserUserAgents = new HashSet();
+        String[] arr = agentsArray;
+        int len = agentsArray.length;
+
+        for (int i = 0; i < len; ++i) {
+            String patternString = arr[i];
+            this.browserUserAgents.add(Pattern.compile(patternString));
+        }
+
+    }
+
+    void parseMethodsToIgnore(String mti) {
+        String[] methods = mti.split(",");
+        this.methodsToIgnore = new HashSet();
+
+        for (int i = 0; i < methods.length; ++i) {
+            this.methodsToIgnore.add(methods[i]);
+        }
+
+    }
+
+    protected boolean isBrowser(String userAgent) {
+        if (userAgent == null) {
+            return false;
+        } else {
+            Iterator iterator = this.browserUserAgents.iterator();
+
+            Matcher matcher;
+            do {
+                if (!iterator.hasNext()) {
+                    return false;
+                }
+
+                Pattern pattern = (Pattern)iterator.next();
+                matcher = pattern.matcher(userAgent);
+            } while(!matcher.matches());
+
+            return true;
+        }
+    }
+
+    public void handleHttpInteraction(RestCsrfPreventionFilter.HttpInteraction httpInteraction)
+        throws IOException, ServletException {
+        if (this.isBrowser(httpInteraction.getHeader(HEADER_USER_AGENT))
+                && !this.methodsToIgnore.contains(httpInteraction.getMethod())
+                && httpInteraction.getHeader(this.headerName) == null) {
+            httpInteraction.sendError(HttpServletResponse.SC_FORBIDDEN, CSRF_ERROR_MESSAGE);
+        } else {
+            httpInteraction.proceed();
+        }
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+        throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpServletResponse httpResponse = (HttpServletResponse)response;
+        this.handleHttpInteraction(new RestCsrfPreventionFilter.ServletFilterHttpInteraction(
+                httpRequest, httpResponse, chain));
+    }
+
+    public void destroy() {
+    }
+
+    private static final class ServletFilterHttpInteraction implements RestCsrfPreventionFilter.HttpInteraction
{
+        private final FilterChain chain;
+        private final HttpServletRequest httpRequest;
+        private final HttpServletResponse httpResponse;
+
+        public ServletFilterHttpInteraction(HttpServletRequest httpRequest,
+                                            HttpServletResponse httpResponse, FilterChain
chain) {
+            this.httpRequest = httpRequest;
+            this.httpResponse = httpResponse;
+            this.chain = chain;
+        }
+
+        public String getHeader(String header) {
+            return this.httpRequest.getHeader(header);
+        }
+
+        public String getMethod() {
+            return this.httpRequest.getMethod();
+        }
+
+        public void proceed() throws IOException, ServletException {
+            this.chain.doFilter(this.httpRequest, this.httpResponse);
+        }
+
+        public void sendError(int code, String message) throws IOException {
+            this.httpResponse.sendError(code, message);
+        }
+    }
+
+    /**
+     * Interface for HttpInteraction.
+     */
+    public interface HttpInteraction {
+        String getHeader(String var1);
+
+        String getMethod();
+
+        void proceed() throws IOException, ServletException;
+
+        void sendError(int var1, String var2) throws IOException;
+    }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/prism/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/prism/src/main/webapp/WEB-INF/web.xml b/prism/src/main/webapp/WEB-INF/web.xml
index dcc114c..c92b757 100644
--- a/prism/src/main/webapp/WEB-INF/web.xml
+++ b/prism/src/main/webapp/WEB-INF/web.xml
@@ -41,6 +41,15 @@
     </filter>
 
     <filter>
+        <filter-name>csrf</filter-name>
+        <filter-class>org.apache.falcon.security.FalconCSRFFilter</filter-class>
+        <init-param>
+            <param-name>methods-to-ignore</param-name>
+            <param-value>GET</param-value>
+        </init-param>
+    </filter>
+
+    <filter>
         <filter-name>authorization</filter-name>
         <filter-class>org.apache.falcon.security.FalconAuthorizationFilter</filter-class>
     </filter>
@@ -61,6 +70,11 @@
     </filter-mapping>
 
     <filter-mapping>
+        <filter-name>csrf</filter-name>
+        <servlet-name>FalconProxyAPI</servlet-name>
+    </filter-mapping>
+
+    <filter-mapping>
         <filter-name>authorization</filter-name>
         <servlet-name>FalconProxyAPI</servlet-name>
     </filter-mapping>

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/prism/src/test/java/org/apache/falcon/security/FalconCSRFFilterTest.java
----------------------------------------------------------------------
diff --git a/prism/src/test/java/org/apache/falcon/security/FalconCSRFFilterTest.java b/prism/src/test/java/org/apache/falcon/security/FalconCSRFFilterTest.java
new file mode 100644
index 0000000..f20e0be
--- /dev/null
+++ b/prism/src/test/java/org/apache/falcon/security/FalconCSRFFilterTest.java
@@ -0,0 +1,138 @@
+/**
+ * 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.falcon.security;
+
+import org.apache.falcon.util.StartupProperties;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.HttpMethod;
+
+/**
+ * Test for FalconCSRFFilter using mock objects.
+ */
+public class FalconCSRFFilterTest {
+    private static final String FALCON_CSRF_HEADER_DEFAULT = "FALCON-CSRF-FILTER";
+
+    @Mock
+    private HttpServletRequest mockRequest;
+
+    @Mock
+    private HttpServletResponse mockResponse;
+
+    @Mock
+    private FilterChain mockChain;
+
+    @Mock
+    private FilterConfig mockConfig;
+
+    @BeforeClass
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testCSRFEnabledAllowedMethodFromBrowser() throws Exception {
+        StartupProperties.get().setProperty("falcon.security.csrf.enabled", "true");
+        mockHeader("Mozilla/5.0", null);
+        mockGetMethod();
+        mockRunFilter();
+        Mockito.verify(mockResponse, Mockito.never()).sendError(HttpServletResponse.SC_FORBIDDEN,
+                RestCsrfPreventionFilter.CSRF_ERROR_MESSAGE);
+    }
+
+    @Test
+    public void testCSRFEnabledNoCustomHeaderFromBrowser() throws Exception {
+        StartupProperties.get().setProperty("falcon.security.csrf.enabled", "true");
+        mockHeader("Mozilla/5.0", null);
+        mockDeleteMethod();
+        mockRunFilter();
+        Mockito.verify(mockResponse).sendError(HttpServletResponse.SC_FORBIDDEN,
+                RestCsrfPreventionFilter.CSRF_ERROR_MESSAGE);
+    }
+
+    @Test
+    public void testCSRFEnabledIncludeCustomHeaderFromBrowser() throws Exception {
+        StartupProperties.get().setProperty("falcon.security.csrf.enabled", "true");
+        mockHeader("Mozilla/5.0", "");
+        mockDeleteMethod();
+        mockRunFilter();
+        Mockito.verify(mockResponse, Mockito.never()).sendError(HttpServletResponse.SC_FORBIDDEN,
+                RestCsrfPreventionFilter.CSRF_ERROR_MESSAGE);
+    }
+
+    @Test
+    public void testCSRFEnabledAllowNonBrowserInteractionWithoutHeader() throws Exception
{
+        StartupProperties.get().setProperty("falcon.security.csrf.enabled", "true");
+        mockHeader(null, null);
+        mockDeleteMethod();
+        mockRunFilter();
+        Mockito.verify(mockResponse, Mockito.never()).sendError(HttpServletResponse.SC_FORBIDDEN,
+                RestCsrfPreventionFilter.CSRF_ERROR_MESSAGE);
+    }
+
+    @Test
+    public void testCSRFDisabledAllowAnyMethodFromBrowser() throws Exception {
+        StartupProperties.get().setProperty("falcon.security.csrf.enabled", "false");
+        mockHeader("Mozilla/5.0", null);
+        mockDeleteMethod();
+        mockRunFilter();
+        Mockito.verify(mockResponse, Mockito.never()).sendError(HttpServletResponse.SC_FORBIDDEN,
+                RestCsrfPreventionFilter.CSRF_ERROR_MESSAGE);
+    }
+
+    private void mockGetMethod() {
+        mockMethod(HttpMethod.GET, "/entities/list");
+    }
+
+    private void mockDeleteMethod() {
+        mockMethod(HttpMethod.DELETE, "/entities/delete/cluster/primaryCluster");
+    }
+
+    private void mockMethod(String method, String resource) {
+        StringBuffer requestUrl = new StringBuffer("http://localhost" + resource);
+        Mockito.when(mockRequest.getRequestURL()).thenReturn(requestUrl);
+        Mockito.when(mockRequest.getRequestURI()).thenReturn("/api" + resource);
+        Mockito.when(mockRequest.getPathInfo()).thenReturn(resource);
+        Mockito.when(mockRequest.getMethod()).thenReturn(method);
+    }
+
+    private void mockHeader(String userAgent, String customHeader) {
+        Mockito.when(mockRequest.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);
+        Mockito.when(mockRequest.getHeader(FALCON_CSRF_HEADER_DEFAULT)).thenReturn(customHeader);
+    }
+
+    private void mockRunFilter() throws Exception {
+        Mockito.when(mockConfig.getInitParameter("methods-to-ignore")).thenReturn("GET");
+        FalconCSRFFilter filter = new FalconCSRFFilter();
+        filter.init(mockConfig);
+        try {
+            filter.doFilter(mockRequest, mockResponse, mockChain);
+        } finally {
+            filter.destroy();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/src/conf/startup.properties
----------------------------------------------------------------------
diff --git a/src/conf/startup.properties b/src/conf/startup.properties
index a47327a..37e37bc 100644
--- a/src/conf/startup.properties
+++ b/src/conf/startup.properties
@@ -281,6 +281,15 @@ prism.configstore.listeners=org.apache.falcon.entity.v0.EntityGraph,\
 # Authorization Enabled flag: false (default)|true
 *.falcon.security.authorization.enabled=false
 
+# CSRF filter enabled flag: false (default) | true
+*.falcon.security.csrf.enabled=false
+
+# Custom header for CSRF filter
+*.falcon.security.csrf.header=FALCON-CSRF-FILTER
+
+# Browser user agents to be filtered
+*.falcon.security.csrf.browser=^Mozilla.*,^Opera.*
+
 # The name of the group of super-users
 *.falcon.security.authorization.superusergroup=falcon
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/webapp/src/main/webapp/WEB-INF/distributed/web.xml
----------------------------------------------------------------------
diff --git a/webapp/src/main/webapp/WEB-INF/distributed/web.xml b/webapp/src/main/webapp/WEB-INF/distributed/web.xml
index e67191f..55fea22 100644
--- a/webapp/src/main/webapp/WEB-INF/distributed/web.xml
+++ b/webapp/src/main/webapp/WEB-INF/distributed/web.xml
@@ -41,6 +41,15 @@
     </filter>
 
     <filter>
+        <filter-name>csrf</filter-name>
+        <filter-class>org.apache.falcon.security.FalconCSRFFilter</filter-class>
+        <init-param>
+            <param-name>methods-to-ignore</param-name>
+            <param-value>GET</param-value>
+        </init-param>
+    </filter>
+
+    <filter>
         <filter-name>authorization</filter-name>
         <filter-class>org.apache.falcon.security.FalconAuthorizationFilter</filter-class>
     </filter>
@@ -66,6 +75,11 @@
     </filter-mapping>
 
     <filter-mapping>
+        <filter-name>csrf</filter-name>
+        <servlet-name>FalconRESTApi</servlet-name>
+    </filter-mapping>
+
+    <filter-mapping>
         <filter-name>authorization</filter-name>
         <servlet-name>FalconRESTApi</servlet-name>
     </filter-mapping>

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/webapp/src/main/webapp/WEB-INF/embedded/web.xml
----------------------------------------------------------------------
diff --git a/webapp/src/main/webapp/WEB-INF/embedded/web.xml b/webapp/src/main/webapp/WEB-INF/embedded/web.xml
index 084f9ab..a7c0b58 100644
--- a/webapp/src/main/webapp/WEB-INF/embedded/web.xml
+++ b/webapp/src/main/webapp/WEB-INF/embedded/web.xml
@@ -41,6 +41,15 @@
     </filter>
 
     <filter>
+        <filter-name>csrf</filter-name>
+        <filter-class>org.apache.falcon.security.FalconCSRFFilter</filter-class>
+        <init-param>
+            <param-name>methods-to-ignore</param-name>
+            <param-value>GET</param-value>
+        </init-param>
+    </filter>
+
+    <filter>
         <filter-name>authorization</filter-name>
         <filter-class>org.apache.falcon.security.FalconAuthorizationFilter</filter-class>
     </filter>
@@ -61,6 +70,11 @@
     </filter-mapping>
 
     <filter-mapping>
+        <filter-name>csrf</filter-name>
+        <servlet-name>FalconRESTApi</servlet-name>
+    </filter-mapping>
+
+    <filter-mapping>
         <filter-name>authorization</filter-name>
         <servlet-name>FalconRESTApi</servlet-name>
     </filter-mapping>

http://git-wip-us.apache.org/repos/asf/falcon/blob/c57c8f44/webapp/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml
index 048d085..26d247f 100644
--- a/webapp/src/main/webapp/WEB-INF/web.xml
+++ b/webapp/src/main/webapp/WEB-INF/web.xml
@@ -41,6 +41,15 @@
     </filter>
 
     <filter>
+        <filter-name>csrf</filter-name>
+        <filter-class>org.apache.falcon.security.FalconCSRFFilter</filter-class>
+        <init-param>
+            <param-name>methods-to-ignore</param-name>
+            <param-value>GET</param-value>
+        </init-param>
+    </filter>
+
+    <filter>
         <filter-name>authorization</filter-name>
         <filter-class>org.apache.falcon.security.FalconAuthorizationFilter</filter-class>
     </filter>
@@ -61,6 +70,11 @@
     </filter-mapping>
 
     <filter-mapping>
+        <filter-name>csrf</filter-name>
+        <servlet-name>FalconRESTApi</servlet-name>
+    </filter-mapping>
+
+    <filter-mapping>
         <filter-name>authorization</filter-name>
         <servlet-name>FalconRESTApi</servlet-name>
     </filter-mapping>


Mime
View raw message