You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by as...@apache.org on 2016/02/11 08:59:42 UTC

[08/50] hadoop git commit: HADOOP-12758. Extend CSRF Filter with UserAgent Checks. Contributed by Larry McCay.

HADOOP-12758. Extend CSRF Filter with UserAgent Checks. Contributed by Larry McCay.


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

Branch: refs/heads/yarn-2877
Commit: a37e423e8407c42988577d87907d13ce0432dda1
Parents: 49e176c
Author: cnauroth <cn...@apache.org>
Authored: Fri Feb 5 14:38:21 2016 -0800
Committer: cnauroth <cn...@apache.org>
Committed: Fri Feb 5 14:38:21 2016 -0800

----------------------------------------------------------------------
 hadoop-common-project/hadoop-common/CHANGES.txt |  3 +
 .../security/http/RestCsrfPreventionFilter.java | 54 +++++++++++-
 .../http/TestRestCsrfPreventionFilter.java      | 93 ++++++++++++++++++--
 3 files changed, 142 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hadoop/blob/a37e423e/hadoop-common-project/hadoop-common/CHANGES.txt
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index 13568e2..3fdce4a 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -1137,6 +1137,9 @@ Release 2.8.0 - UNRELEASED
     HADOOP-12450. UserGroupInformation should not log at WARN level if no groups
     are found. (Elliott Clark via stevel)
 
+    HADOOP-12758. Extend CSRF Filter with UserAgent Checks
+    (Larry McCay via cnauroth)
+
   BUG FIXES
 
     HADOOP-12352. Delay in checkpointing Trash can leave trash for 2 intervals

http://git-wip-us.apache.org/repos/asf/hadoop/blob/a37e423e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java
index 50f95ad..4f7f5bb 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java
@@ -19,6 +19,8 @@ package org.apache.hadoop.security.http;
 
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.Set;
 
 import javax.servlet.Filter;
@@ -38,13 +40,18 @@ import javax.servlet.http.HttpServletResponse;
  * attempt as a bad request.
  */
 public class RestCsrfPreventionFilter implements Filter {
+  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.*";
   static final String HEADER_DEFAULT = "X-XSRF-HEADER";
   static final String  METHODS_TO_IGNORE_DEFAULT = "GET,OPTIONS,HEAD,TRACE";
   private String  headerName = HEADER_DEFAULT;
   private Set<String> methodsToIgnore = null;
+  private Set<Pattern> browserUserAgents;
 
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
@@ -59,6 +66,20 @@ public class RestCsrfPreventionFilter implements Filter {
     } else {
       parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT);
     }
+
+    String agents = filterConfig.getInitParameter(BROWSER_USER_AGENT_PARAM);
+    if (agents == null) {
+      agents = BROWSER_USER_AGENTS_DEFAULT;
+    }
+    parseBrowserUserAgents(agents);
+  }
+
+  void parseBrowserUserAgents(String userAgents) {
+    String[] agentsArray =  userAgents.split(",");
+    browserUserAgents = new HashSet<Pattern>();
+    for (String patternString : agentsArray) {
+      browserUserAgents.add(Pattern.compile(patternString));
+    }
   }
 
   void parseMethodsToIgnore(String mti) {
@@ -69,17 +90,46 @@ public class RestCsrfPreventionFilter implements Filter {
     }
   }
 
+  /**
+   * This method interrogates the User-Agent String and returns whether it
+   * refers to a browser.  If its not a browser, then the requirement for the
+   * CSRF header will not be enforced; if it is a browser, the requirement will
+   * be enforced.
+   * <p>
+   * A User-Agent String is considered to be a browser if it matches
+   * any of the regex patterns from browser-useragent-regex; the default
+   * behavior is to consider everything a browser that matches the following:
+   * "^Mozilla.*,^Opera.*".  Subclasses can optionally override
+   * this method to use different behavior.
+   *
+   * @param userAgent The User-Agent String, or null if there isn't one
+   * @return true if the User-Agent String refers to a browser, false if not
+   */
+  protected boolean isBrowser(String userAgent) {
+    if (userAgent == null) {
+      return false;
+    }
+    for (Pattern pattern : browserUserAgents) {
+      Matcher matcher = pattern.matcher(userAgent);
+      if (matcher.matches()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   @Override
   public void doFilter(ServletRequest request, ServletResponse response,
       FilterChain chain) throws IOException, ServletException {
     HttpServletRequest httpRequest = (HttpServletRequest)request;
-    if (methodsToIgnore.contains(httpRequest.getMethod()) ||
+    if (!isBrowser(httpRequest.getHeader(HEADER_USER_AGENT)) ||
+        methodsToIgnore.contains(httpRequest.getMethod()) ||
         httpRequest.getHeader(headerName) != null) {
       chain.doFilter(request, response);
     } else {
       ((HttpServletResponse)response).sendError(
           HttpServletResponse.SC_BAD_REQUEST,
-          "Missing Required Header for Vulnerability Protection");
+          "Missing Required Header for CSRF Vulnerability Protection");
     }
   }
 

http://git-wip-us.apache.org/repos/asf/hadoop/blob/a37e423e/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java
index adf89f5..29dccd3 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java
@@ -34,8 +34,12 @@ import org.mockito.Mockito;
 
 public class TestRestCsrfPreventionFilter {
 
+  private static final String NON_BROWSER = "java";
+  private static final String BROWSER_AGENT =
+      "Mozilla/5.0 (compatible; U; ABrowse 0.6; Syllable)" +
+      " AppleWebKit/420+ (KHTML, like Gecko)";
   private static final String EXPECTED_MESSAGE =
-      "Missing Required Header for Vulnerability Protection";
+      "Missing Required Header for CSRF Vulnerability Protection";
   private static final String X_CUSTOM_HEADER = "X-CUSTOM_HEADER";
 
   @Test
@@ -52,7 +56,44 @@ public class TestRestCsrfPreventionFilter {
     // CSRF has not been sent
     HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
+    thenReturn(null);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+      thenReturn(BROWSER_AGENT);
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    verify(mockRes, atLeastOnce()).sendError(
+        HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE);
+    Mockito.verifyZeroInteractions(mockChain);
+  }
+
+  @Test
+  public void testNoHeaderCustomAgentConfig_badRequest()
+      throws ServletException, IOException {
+    // Setup the configuration settings of the server
+    FilterConfig filterConfig = Mockito.mock(FilterConfig.class);
+    Mockito.when(filterConfig.getInitParameter(
+      RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null);
+    Mockito.when(filterConfig.getInitParameter(
+      RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn(null);
+    Mockito.when(filterConfig.getInitParameter(
+        RestCsrfPreventionFilter.BROWSER_USER_AGENT_PARAM)).
+        thenReturn("^Mozilla.*,^Opera.*,curl");
+
+    // CSRF has not been sent
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
+    thenReturn(null);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+      thenReturn("curl");
 
     // Objects to verify interactions based on request
     HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
@@ -69,6 +110,36 @@ public class TestRestCsrfPreventionFilter {
   }
 
   @Test
+  public void testNoHeaderDefaultConfigNonBrowser_goodRequest()
+      throws ServletException, IOException {
+    // Setup the configuration settings of the server
+    FilterConfig filterConfig = Mockito.mock(FilterConfig.class);
+    Mockito.when(filterConfig.getInitParameter(
+      RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null);
+    Mockito.when(filterConfig.getInitParameter(
+      RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
+      thenReturn(null);
+
+    // CSRF has not been sent
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
+    thenReturn(null);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+      thenReturn(NON_BROWSER);
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
   public void testHeaderPresentDefaultConfig_goodRequest()
       throws ServletException, IOException {
     // Setup the configuration settings of the server
@@ -136,9 +207,11 @@ public class TestRestCsrfPreventionFilter {
     Mockito.when(filterConfig.getInitParameter(
       RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn(null);
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+    thenReturn(BROWSER_AGENT);
 
     // CSRF has not been sent
-    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
       thenReturn(null);
 
@@ -164,9 +237,11 @@ public class TestRestCsrfPreventionFilter {
     Mockito.when(filterConfig.getInitParameter(
       RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn("");
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+    thenReturn(BROWSER_AGENT);
 
     // CSRF has not been sent
-    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
       thenReturn(null);
     Mockito.when(mockReq.getMethod()).
@@ -194,9 +269,11 @@ public class TestRestCsrfPreventionFilter {
     Mockito.when(filterConfig.getInitParameter(
       RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn("GET");
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+    thenReturn(BROWSER_AGENT);
 
     // CSRF has not been sent
-    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
       thenReturn(null);
     Mockito.when(mockReq.getMethod()).
@@ -224,9 +301,11 @@ public class TestRestCsrfPreventionFilter {
     Mockito.when(filterConfig.getInitParameter(
       RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn("GET,OPTIONS");
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+    thenReturn(BROWSER_AGENT);
 
     // CSRF has not been sent
-    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
       thenReturn(null);
     Mockito.when(mockReq.getMethod()).
@@ -254,9 +333,11 @@ public class TestRestCsrfPreventionFilter {
     Mockito.when(filterConfig.getInitParameter(
       RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)).
       thenReturn("GET,OPTIONS");
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)).
+    thenReturn(BROWSER_AGENT);
 
     // CSRF has not been sent
-    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
     Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)).
       thenReturn(null);
     Mockito.when(mockReq.getMethod()).