/** * 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.token.delegation.web; import com.google.common.annotations.VisibleForTesting; import org.apache.curator.framework.CuratorFramework; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.SaslRpcServer; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authentication.server.AuthenticationFilter; import org.apache.hadoop.security.authentication.server.AuthenticationHandler; import org.apache.hadoop.security.authentication.server.AuthenticationToken; import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler; import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler; import org.apache.hadoop.security.authentication.util.ZKSignerSecretProvider; import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager; import org.apache.hadoop.security.token.delegation.ZKDelegationTokenSecretManager; import org.apache.hadoop.util.HttpExceptionUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.codehaus.jackson.map.ObjectMapper; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.security.Principal; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; /** * The <code>DelegationTokenAuthenticationFilter</code> filter is a * {@link AuthenticationFilter} with Hadoop Delegation Token support. * <p/> * By default it uses it own instance of the {@link * AbstractDelegationTokenSecretManager}. For situations where an external * <code>AbstractDelegationTokenSecretManager</code> is required (i.e. one that * shares the secret with <code>AbstractDelegationTokenSecretManager</code> * instance running in other services), the external * <code>AbstractDelegationTokenSecretManager</code> must be set as an * attribute in the {@link ServletContext} of the web application using the * {@link #DELEGATION_TOKEN_SECRET_MANAGER_ATTR} attribute name ( * 'hadoop.http.delegation-token-secret-manager'). */ @InterfaceAudience.Private @InterfaceStability.Evolving public class DelegationTokenAuthenticationFilter extends AuthenticationFilter { private static final String APPLICATION_JSON_MIME = "application/json"; private static final String ERROR_EXCEPTION_JSON = "exception"; private static final String ERROR_MESSAGE_JSON = "message"; /** * Sets an external <code>DelegationTokenSecretManager</code> instance to * manage creation and verification of Delegation Tokens. * <p/> * This is useful for use cases where secrets must be shared across multiple * services. */ public static final String DELEGATION_TOKEN_SECRET_MANAGER_ATTR = "hadoop.http.delegation-token-secret-manager"; private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); private static final ThreadLocal<UserGroupInformation> UGI_TL = new ThreadLocal<UserGroupInformation>(); public static final String PROXYUSER_PREFIX = "proxyuser"; private SaslRpcServer.AuthMethod handlerAuthMethod; /** * It delegates to * {@link AuthenticationFilter#getConfiguration(String, FilterConfig)} and * then overrides the {@link AuthenticationHandler} to use if authentication * type is set to <code>simple</code> or <code>kerberos</code> in order to use * the corresponding implementation with delegation token support. * * @param configPrefix parameter not used. * @param filterConfig parameter not used. * @return hadoop-auth de-prefixed configuration for the filter and handler. */ @Override protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException { Properties props = super.getConfiguration(configPrefix, filterConfig); setAuthHandlerClass(props); return props; } /** * Set AUTH_TYPE property to the name of the corresponding authentication * handler class based on the input properties. * @param props input properties. */ protected void setAuthHandlerClass(Properties props) throws ServletException { String authType = props.getProperty(AUTH_TYPE); if (authType == null) { throw new ServletException("Config property " + AUTH_TYPE + " doesn't exist"); } if (authType.equals(PseudoAuthenticationHandler.TYPE)) { props.setProperty(AUTH_TYPE, PseudoDelegationTokenAuthenticationHandler.class.getName()); } else if (authType.equals(KerberosAuthenticationHandler.TYPE)) { props.setProperty(AUTH_TYPE, KerberosDelegationTokenAuthenticationHandler.class.getName()); } } /** * Returns the proxyuser configuration. All returned properties must start * with <code>proxyuser.</code>' * <p/> * Subclasses may override this method if the proxyuser configuration is * read from other place than the filter init parameters. * * @param filterConfig filter configuration object * @return the proxyuser configuration properties. * @throws ServletException thrown if the configuration could not be created. */ protected Configuration getProxyuserConfiguration(FilterConfig filterConfig) throws ServletException { // this filter class gets the configuration from the filter configs, we are // creating an empty configuration and injecting the proxyuser settings in // it. In the initialization of the filter, the returned configuration is // passed to the ProxyUsers which only looks for 'proxyusers.' properties. Configuration conf = new Configuration(false); Enumeration<?> names = filterConfig.getInitParameterNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); if (name.startsWith(PROXYUSER_PREFIX + ".")) { String value = filterConfig.getInitParameter(name); conf.set(name, value); } } return conf; } @Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); AuthenticationHandler handler = getAuthenticationHandler(); AbstractDelegationTokenSecretManager dtSecretManager = (AbstractDelegationTokenSecretManager) filterConfig.getServletContext(). getAttribute(DELEGATION_TOKEN_SECRET_MANAGER_ATTR); if (dtSecretManager != null && handler instanceof DelegationTokenAuthenticationHandler) { DelegationTokenAuthenticationHandler dtHandler = (DelegationTokenAuthenticationHandler) getAuthenticationHandler(); dtHandler.setExternalDelegationTokenSecretManager(dtSecretManager); } if (handler instanceof PseudoAuthenticationHandler || handler instanceof PseudoDelegationTokenAuthenticationHandler) { setHandlerAuthMethod(SaslRpcServer.AuthMethod.SIMPLE); } if (handler instanceof KerberosAuthenticationHandler || handler instanceof KerberosDelegationTokenAuthenticationHandler) { setHandlerAuthMethod(SaslRpcServer.AuthMethod.KERBEROS); } // proxyuser configuration Configuration conf = getProxyuserConfiguration(filterConfig); ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX); } @Override protected void initializeAuthHandler(String authHandlerClassName, FilterConfig filterConfig) throws ServletException { // A single CuratorFramework should be used for a ZK cluster. // If the ZKSignerSecretProvider has already created it, it has to // be set here... to be used by the ZKDelegationTokenSecretManager ZKDelegationTokenSecretManager.setCurator((CuratorFramework) filterConfig.getServletContext().getAttribute(ZKSignerSecretProvider. ZOOKEEPER_SIGNER_SECRET_PROVIDER_CURATOR_CLIENT_ATTRIBUTE)); super.initializeAuthHandler(authHandlerClassName, filterConfig); ZKDelegationTokenSecretManager.setCurator(null); } protected void setHandlerAuthMethod(SaslRpcServer.AuthMethod authMethod) { this.handlerAuthMethod = authMethod; } @VisibleForTesting static String getDoAs(HttpServletRequest request) { String queryString = request.getQueryString(); if (queryString == null) { return null; } List<NameValuePair> list = URLEncodedUtils.parse(queryString, UTF8_CHARSET); if (list != null) { for (NameValuePair nv : list) { if (DelegationTokenAuthenticatedURL.DO_AS. equalsIgnoreCase(nv.getName())) { return nv.getValue(); } } } return null; } static UserGroupInformation getHttpUserGroupInformationInContext() { return UGI_TL.get(); } @Override protected void doFilter(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { boolean requestCompleted = false; UserGroupInformation ugi = null; AuthenticationToken authToken = (AuthenticationToken) request.getUserPrincipal(); if (authToken != null && authToken != AuthenticationToken.ANONYMOUS) { // if the request was authenticated because of a delegation token, // then we ignore proxyuser (this is the same as the RPC behavior). ugi = (UserGroupInformation) request.getAttribute( DelegationTokenAuthenticationHandler.DELEGATION_TOKEN_UGI_ATTRIBUTE); if (ugi == null) { String realUser = request.getUserPrincipal().getName(); ugi = UserGroupInformation.createRemoteUser(realUser, handlerAuthMethod); String doAsUser = getDoAs(request); if (doAsUser != null) { ugi = UserGroupInformation.createProxyUser(doAsUser, ugi); try { ProxyUsers.authorize(ugi, request.getRemoteHost()); } catch (AuthorizationException ex) { HttpExceptionUtils.createServletExceptionResponse(response, HttpServletResponse.SC_FORBIDDEN, ex); requestCompleted = true; } } } UGI_TL.set(ugi); } if (!requestCompleted) { final UserGroupInformation ugiF = ugi; try { request = new HttpServletRequestWrapper(request) { @Override public String getAuthType() { return (ugiF != null) ? handlerAuthMethod.toString() : null; } @Override public String getRemoteUser() { return (ugiF != null) ? ugiF.getShortUserName() : null; } @Override public Principal getUserPrincipal() { return (ugiF != null) ? new Principal() { @Override public String getName() { return ugiF.getUserName(); } } : null; } }; super.doFilter(filterChain, request, response); } finally { UGI_TL.remove(); } } } }