All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.security.http.RestCsrfPreventionFilter Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/**
 * 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.security.http;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Set;

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;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;

import org.eclipse.jetty.server.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This filter provides protection against cross site request forgery (CSRF)
 * attacks for REST APIs. Enabling this filter on an endpoint results in the
 * requirement of all client to send a particular (configurable) HTTP header
 * with every request. In the absense of this header the filter will reject the
 * attempt as a bad request.
 */
@InterfaceAudience.Public
@InterfaceStability.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";
  private String  headerName = HEADER_DEFAULT;
  private Set methodsToIgnore = null;
  private Set browserUserAgents;

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    String customHeader = filterConfig.getInitParameter(CUSTOM_HEADER_PARAM);
    if (customHeader != null) {
      headerName = customHeader;
    }
    String customMethodsToIgnore =
        filterConfig.getInitParameter(CUSTOM_METHODS_TO_IGNORE_PARAM);
    if (customMethodsToIgnore != null) {
      parseMethodsToIgnore(customMethodsToIgnore);
    } else {
      parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT);
    }

    String agents = filterConfig.getInitParameter(BROWSER_USER_AGENT_PARAM);
    if (agents == null) {
      agents = BROWSER_USER_AGENTS_DEFAULT;
    }
    parseBrowserUserAgents(agents);
    LOG.info("Adding cross-site request forgery (CSRF) protection, "
        + "headerName = {}, methodsToIgnore = {}, browserUserAgents = {}",
        headerName, methodsToIgnore, browserUserAgents);
  }

  void parseBrowserUserAgents(String userAgents) {
    String[] agentsArray =  userAgents.split(",");
    browserUserAgents = new HashSet();
    for (String patternString : agentsArray) {
      browserUserAgents.add(Pattern.compile(patternString));
    }
  }

  void parseMethodsToIgnore(String mti) {
    String[] methods = mti.split(",");
    methodsToIgnore = new HashSet();
    for (int i = 0; i < methods.length; i++) {
      methodsToIgnore.add(methods[i]);
    }
  }

  /**
   * 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.
   * 

* 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; } /** * Defines the minimal API requirements for the filter to execute its * filtering logic. This interface exists to facilitate integration in * components that do not run within a servlet container and therefore cannot * rely on a servlet container to dispatch to the {@link #doFilter} method. * Applications that do run inside a servlet container will not need to write * code that uses this interface. Instead, they can use typical servlet * container configuration mechanisms to insert the filter. */ public interface HttpInteraction { /** * Returns the value of a header. * * @param header name of header * @return value of header */ String getHeader(String header); /** * Returns the method. * * @return method */ String getMethod(); /** * Called by the filter after it decides that the request may proceed. * * @throws IOException if there is an I/O error * @throws ServletException if the implementation relies on the servlet API * and a servlet API call has failed */ void proceed() throws IOException, ServletException; /** * Called by the filter after it decides that the request is a potential * CSRF attack and therefore must be rejected. * * @param code status code to send * @param message response message * @throws IOException if there is an I/O error */ void sendError(int code, String message) throws IOException; } /** * Handles an {@link HttpInteraction} by applying the filtering logic. * * @param httpInteraction caller's HTTP interaction * @throws IOException if there is an I/O error * @throws ServletException if the implementation relies on the servlet API * and a servlet API call has failed */ public void handleHttpInteraction(HttpInteraction httpInteraction) throws IOException, ServletException { if (!isBrowser(httpInteraction.getHeader(HEADER_USER_AGENT)) || methodsToIgnore.contains(httpInteraction.getMethod()) || httpInteraction.getHeader(headerName) != null) { httpInteraction.proceed(); } else { httpInteraction.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing Required Header for CSRF Vulnerability Protection"); } } @Override public void doFilter(ServletRequest request, ServletResponse response, final FilterChain chain) throws IOException, ServletException { final HttpServletRequest httpRequest = (HttpServletRequest)request; final HttpServletResponse httpResponse = (HttpServletResponse)response; handleHttpInteraction(new ServletFilterHttpInteraction(httpRequest, httpResponse, chain)); } @Override public void destroy() { } /** * Constructs a mapping of configuration properties to be used for filter * initialization. The mapping includes all properties that start with the * specified configuration prefix. Property names in the mapping are trimmed * to remove the configuration prefix. * * @param conf configuration to read * @param confPrefix configuration prefix * @return mapping of configuration properties to be used for filter * initialization */ public static Map getFilterParams(Configuration conf, String confPrefix) { return conf.getPropsWithPrefix(confPrefix); } /** * {@link HttpInteraction} implementation for use in the servlet filter. */ private static final class ServletFilterHttpInteraction implements HttpInteraction { private final FilterChain chain; private final HttpServletRequest httpRequest; private final HttpServletResponse httpResponse; /** * Creates a new ServletFilterHttpInteraction. * * @param httpRequest request to process * @param httpResponse response to process * @param chain filter chain to forward to if HTTP interaction is allowed */ public ServletFilterHttpInteraction(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) { this.httpRequest = httpRequest; this.httpResponse = httpResponse; this.chain = chain; } @Override public String getHeader(String header) { return httpRequest.getHeader(header); } @Override public String getMethod() { return httpRequest.getMethod(); } @Override public void proceed() throws IOException, ServletException { chain.doFilter(httpRequest, httpResponse); } @Override public void sendError(int code, String message) throws IOException { if (httpResponse instanceof Response) { ((Response)httpResponse).setStatusWithReason(code, message); } httpResponse.sendError(code, message); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy