001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import java.io.IOException;
021import java.security.Principal;
022import java.util.ArrayList;
023import java.util.Enumeration;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import javax.servlet.FilterChain;
029import javax.servlet.FilterConfig;
030import javax.servlet.ServletException;
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletRequestWrapper;
033import javax.servlet.http.HttpServletResponse;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.security.UserGroupInformation;
036import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
037import org.apache.hadoop.security.authorize.AuthorizationException;
038import org.apache.hadoop.security.authorize.ProxyUsers;
039import org.apache.hadoop.util.HttpExceptionUtils;
040import org.apache.hadoop.util.StringUtils;
041import org.apache.yetus.audience.InterfaceAudience;
042import org.apache.yetus.audience.InterfaceStability;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046/**
047 * This file has been copied directly (changing only the package name and and the ASF license text
048 * format, and adding the Yetus annotations) from Hadoop, as the Hadoop version that HBase depends
049 * on doesn't have it yet (as of 2020 Apr 24, there is no Hadoop release that has it either). Hadoop
050 * version: unreleased, master branch commit 4ea6c2f457496461afc63f38ef4cef3ab0efce49 Haddop path:
051 * hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/
052 * server/ProxyUserAuthenticationFilter.java AuthenticationFilter which adds support to perform
053 * operations using end user instead of proxy user. Fetches the end user from doAs Query Parameter.
054 */
055@InterfaceAudience.Private
056@InterfaceStability.Evolving
057public class ProxyUserAuthenticationFilter extends AuthenticationFilter {
058
059  private static final Logger LOG = LoggerFactory.getLogger(ProxyUserAuthenticationFilter.class);
060
061  private static final String DO_AS = "doas";
062  public static final String PROXYUSER_PREFIX = "proxyuser";
063
064  @Override
065  public void init(FilterConfig filterConfig) throws ServletException {
066    Configuration conf = getProxyuserConfiguration(filterConfig);
067    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
068    super.init(filterConfig);
069  }
070
071  @Override
072  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
073    HttpServletResponse response) throws IOException, ServletException {
074    final HttpServletRequest lowerCaseRequest = toLowerCase(request);
075    String doAsUser = lowerCaseRequest.getParameter(DO_AS);
076
077    if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
078      LOG.debug("doAsUser = {}, RemoteUser = {} , RemoteAddress = {} ", doAsUser,
079        request.getRemoteUser(), request.getRemoteAddr());
080      UserGroupInformation requestUgi = (request.getUserPrincipal() != null)
081        ? UserGroupInformation.createRemoteUser(request.getRemoteUser())
082        : null;
083      if (requestUgi != null) {
084        requestUgi = UserGroupInformation.createProxyUser(doAsUser, requestUgi);
085        try {
086          ProxyUsers.authorize(requestUgi, request.getRemoteAddr());
087
088          final UserGroupInformation ugiF = requestUgi;
089          request = new HttpServletRequestWrapper(request) {
090            @Override
091            public String getRemoteUser() {
092              return ugiF.getShortUserName();
093            }
094
095            @Override
096            public Principal getUserPrincipal() {
097              return new Principal() {
098                @Override
099                public String getName() {
100                  return ugiF.getUserName();
101                }
102              };
103            }
104          };
105          LOG.debug("Proxy user Authentication successful");
106        } catch (AuthorizationException ex) {
107          HttpExceptionUtils.createServletExceptionResponse(response,
108            HttpServletResponse.SC_FORBIDDEN, ex);
109          LOG.warn("Proxy user Authentication exception", ex);
110          return;
111        }
112      }
113    }
114    super.doFilter(filterChain, request, response);
115  }
116
117  protected Configuration getProxyuserConfiguration(FilterConfig filterConfig)
118    throws ServletException {
119    Configuration conf = new Configuration(false);
120    Enumeration<?> names = filterConfig.getInitParameterNames();
121    while (names.hasMoreElements()) {
122      String name = (String) names.nextElement();
123      if (name.startsWith(PROXYUSER_PREFIX + ".")) {
124        String value = filterConfig.getInitParameter(name);
125        conf.set(name, value);
126      }
127    }
128    return conf;
129  }
130
131  static boolean containsUpperCase(final Iterable<String> strings) {
132    for (String s : strings) {
133      for (int i = 0; i < s.length(); i++) {
134        if (Character.isUpperCase(s.charAt(i))) {
135          return true;
136        }
137      }
138    }
139    return false;
140  }
141
142  /**
143   * The purpose of this function is to get the doAs parameter of a http request case insensitively
144   * @return doAs parameter if exists or null otherwise
145   */
146  public static String getDoasFromHeader(final HttpServletRequest request) {
147    String doas = null;
148    final Enumeration<String> headers = request.getHeaderNames();
149    while (headers.hasMoreElements()) {
150      String header = headers.nextElement();
151      if (header.toLowerCase().equals("doas")) {
152        doas = request.getHeader(header);
153        break;
154      }
155    }
156    return doas;
157  }
158
159  public static HttpServletRequest toLowerCase(final HttpServletRequest request) {
160    @SuppressWarnings("unchecked")
161    final Map<String, String[]> original = (Map<String, String[]>) request.getParameterMap();
162    if (!containsUpperCase(original.keySet())) {
163      return request;
164    }
165
166    final Map<String, List<String>> m = new HashMap<String, List<String>>();
167    for (Map.Entry<String, String[]> entry : original.entrySet()) {
168      final String key = StringUtils.toLowerCase(entry.getKey());
169      List<String> strings = m.get(key);
170      if (strings == null) {
171        strings = new ArrayList<String>();
172        m.put(key, strings);
173      }
174      for (String v : entry.getValue()) {
175        strings.add(v);
176      }
177    }
178
179    return new HttpServletRequestWrapper(request) {
180      private Map<String, String[]> parameters = null;
181
182      @Override
183      public Map<String, String[]> getParameterMap() {
184        if (parameters == null) {
185          parameters = new HashMap<String, String[]>();
186          for (Map.Entry<String, List<String>> entry : m.entrySet()) {
187            final List<String> a = entry.getValue();
188            parameters.put(entry.getKey(), a.toArray(new String[a.size()]));
189          }
190        }
191        return parameters;
192      }
193
194      @Override
195      public String getParameter(String name) {
196        final List<String> a = m.get(name);
197        return a == null ? null : a.get(0);
198      }
199
200      @Override
201      public String[] getParameterValues(String name) {
202        return getParameterMap().get(name);
203      }
204
205      @Override
206      public Enumeration<String> getParameterNames() {
207        final Iterator<String> i = m.keySet().iterator();
208        return new Enumeration<String>() {
209          @Override
210          public boolean hasMoreElements() {
211            return i.hasNext();
212          }
213
214          @Override
215          public String nextElement() {
216            return i.next();
217          }
218        };
219      }
220    };
221  }
222
223}