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}