diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index d113c4a090..57215fbe14 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -597,6 +597,9 @@ Release 2.6.0 - UNRELEASED HADOOP-11181. Generalized o.a.h.s.t.d.DelegationTokenManager to handle all sub-classes of AbstractDelegationTokenIdentifier. (zjshen) + HADOOP-11207. Enhanced common DelegationTokenAuthenticationHandler to support + proxy-users on Delegation-token management operations. (Zhijie Shen via + vinodkv) OPTIMIZATIONS diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticatedURL.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticatedURL.java index 8c7cbdf0e7..7ed0f2647c 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticatedURL.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticatedURL.java @@ -333,6 +333,7 @@ public HttpURLConnection openConnection(URL url, Token token, String doAs) * supported. * @param token the authentication token being used for the user where the * Delegation token will be stored. + * @param renewer the renewer user. * @return a delegation token. * @throws IOException if an IO error occurred. * @throws AuthenticationException if an authentication exception occurred. @@ -340,12 +341,32 @@ public HttpURLConnection openConnection(URL url, Token token, String doAs) public org.apache.hadoop.security.token.Token getDelegationToken(URL url, Token token, String renewer) throws IOException, AuthenticationException { + return getDelegationToken(url, token, renewer, null); + } + + /** + * Requests a delegation token using the configured Authenticator + * for authentication. + * + * @param url the URL to get the delegation token from. Only HTTP/S URLs are + * supported. + * @param token the authentication token being used for the user where the + * Delegation token will be stored. + * @param renewer the renewer user. + * @param doAsUser the user to do as, which will be the token owner. + * @return a delegation token. + * @throws IOException if an IO error occurred. + * @throws AuthenticationException if an authentication exception occurred. + */ + public org.apache.hadoop.security.token.Token + getDelegationToken(URL url, Token token, String renewer, String doAsUser) + throws IOException, AuthenticationException { Preconditions.checkNotNull(url, "url"); Preconditions.checkNotNull(token, "token"); try { token.delegationToken = ((KerberosDelegationTokenAuthenticator) getAuthenticator()). - getDelegationToken(url, token, renewer); + getDelegationToken(url, token, renewer, doAsUser); return token.delegationToken; } catch (IOException ex) { token.delegationToken = null; @@ -365,13 +386,29 @@ public HttpURLConnection openConnection(URL url, Token token, String doAs) */ public long renewDelegationToken(URL url, Token token) throws IOException, AuthenticationException { + return renewDelegationToken(url, token, null); + } + + /** + * Renews a delegation token from the server end-point using the + * configured Authenticator for authentication. + * + * @param url the URL to renew the delegation token from. Only HTTP/S URLs are + * supported. + * @param token the authentication token with the Delegation Token to renew. + * @param doAsUser the user to do as, which will be the token owner. + * @throws IOException if an IO error occurred. + * @throws AuthenticationException if an authentication exception occurred. + */ + public long renewDelegationToken(URL url, Token token, String doAsUser) + throws IOException, AuthenticationException { Preconditions.checkNotNull(url, "url"); Preconditions.checkNotNull(token, "token"); Preconditions.checkNotNull(token.delegationToken, "No delegation token available"); try { return ((KerberosDelegationTokenAuthenticator) getAuthenticator()). - renewDelegationToken(url, token, token.delegationToken); + renewDelegationToken(url, token, token.delegationToken, doAsUser); } catch (IOException ex) { token.delegationToken = null; throw ex; @@ -389,13 +426,28 @@ public long renewDelegationToken(URL url, Token token) */ public void cancelDelegationToken(URL url, Token token) throws IOException { + cancelDelegationToken(url, token, null); + } + + /** + * Cancels a delegation token from the server end-point. It does not require + * being authenticated by the configured Authenticator. + * + * @param url the URL to cancel the delegation token from. Only HTTP/S URLs + * are supported. + * @param token the authentication token with the Delegation Token to cancel. + * @param doAsUser the user to do as, which will be the token owner. + * @throws IOException if an IO error occurred. + */ + public void cancelDelegationToken(URL url, Token token, String doAsUser) + throws IOException { Preconditions.checkNotNull(url, "url"); Preconditions.checkNotNull(token, "token"); Preconditions.checkNotNull(token.delegationToken, "No delegation token available"); try { ((KerberosDelegationTokenAuthenticator) getAuthenticator()). - cancelDelegationToken(url, token, token.delegationToken); + cancelDelegationToken(url, token, token.delegationToken, doAsUser); } finally { token.delegationToken = null; } diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticationHandler.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticationHandler.java index 8321c5e73f..c18b5d32cf 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticationHandler.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticationHandler.java @@ -17,26 +17,6 @@ */ package org.apache.hadoop.security.token.delegation.web; -import com.google.common.annotations.VisibleForTesting; -import org.apache.hadoop.classification.InterfaceAudience; -import org.apache.hadoop.classification.InterfaceStability; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.authentication.client.AuthenticationException; -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.token.Token; -import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier; -import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager; -import org.apache.hadoop.util.HttpExceptionUtils; -import org.codehaus.jackson.map.ObjectMapper; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.MediaType; import java.io.IOException; import java.io.Writer; import java.text.MessageFormat; @@ -47,6 +27,30 @@ import java.util.Properties; import java.util.Set; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +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.authorize.AuthorizationException; +import org.apache.hadoop.security.authorize.ProxyUsers; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier; +import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager; +import org.apache.hadoop.util.HttpExceptionUtils; +import org.codehaus.jackson.map.ObjectMapper; + +import com.google.common.annotations.VisibleForTesting; + /** * An {@link AuthenticationHandler} that implements Kerberos SPNEGO mechanism * for HTTP and supports Delegation Token functionality. @@ -188,6 +192,19 @@ public boolean managementOperation(AuthenticationToken token, UserGroupInformation requestUgi = (token != null) ? UserGroupInformation.createRemoteUser(token.getUserName()) : null; + // Create the proxy user if doAsUser exists + String doAsUser = DelegationTokenAuthenticationFilter.getDoAs(request); + if (requestUgi != null && doAsUser != null) { + requestUgi = UserGroupInformation.createProxyUser( + doAsUser, requestUgi); + try { + ProxyUsers.authorize(requestUgi, request.getRemoteHost()); + } catch (AuthorizationException ex) { + HttpExceptionUtils.createServletExceptionResponse(response, + HttpServletResponse.SC_FORBIDDEN, ex); + return false; + } + } Map map = null; switch (dtOp) { case GETDELEGATIONTOKEN: diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticator.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticator.java index c614ee314c..d93f7acf6c 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticator.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/web/DelegationTokenAuthenticator.java @@ -136,14 +136,35 @@ public void authenticate(URL url, AuthenticatedURL.Token token) * supported. * @param token the authentication token being used for the user where the * Delegation token will be stored. + * @param renewer the renewer user. * @throws IOException if an IO error occurred. * @throws AuthenticationException if an authentication exception occurred. */ public Token getDelegationToken(URL url, AuthenticatedURL.Token token, String renewer) throws IOException, AuthenticationException { + return getDelegationToken(url, token, renewer, null); + } + + /** + * Requests a delegation token using the configured Authenticator + * for authentication. + * + * @param url the URL to get the delegation token from. Only HTTP/S URLs are + * supported. + * @param token the authentication token being used for the user where the + * Delegation token will be stored. + * @param renewer the renewer user. + * @param doAsUser the user to do as, which will be the token owner. + * @throws IOException if an IO error occurred. + * @throws AuthenticationException if an authentication exception occurred. + */ + public Token getDelegationToken(URL url, + AuthenticatedURL.Token token, String renewer, String doAsUser) + throws IOException, AuthenticationException { Map json = doDelegationTokenOperation(url, token, - DelegationTokenOperation.GETDELEGATIONTOKEN, renewer, null, true); + DelegationTokenOperation.GETDELEGATIONTOKEN, renewer, null, true, + doAsUser); json = (Map) json.get(DELEGATION_TOKEN_JSON); String tokenStr = (String) json.get(DELEGATION_TOKEN_URL_STRING_JSON); Token dToken = @@ -169,8 +190,27 @@ public long renewDelegationToken(URL url, AuthenticatedURL.Token token, Token dToken) throws IOException, AuthenticationException { + return renewDelegationToken(url, token, dToken, null); + } + + /** + * Renews a delegation token from the server end-point using the + * configured Authenticator for authentication. + * + * @param url the URL to renew the delegation token from. Only HTTP/S URLs are + * supported. + * @param token the authentication token with the Delegation Token to renew. + * @param doAsUser the user to do as, which will be the token owner. + * @throws IOException if an IO error occurred. + * @throws AuthenticationException if an authentication exception occurred. + */ + public long renewDelegationToken(URL url, + AuthenticatedURL.Token token, + Token dToken, String doAsUser) + throws IOException, AuthenticationException { Map json = doDelegationTokenOperation(url, token, - DelegationTokenOperation.RENEWDELEGATIONTOKEN, null, dToken, true); + DelegationTokenOperation.RENEWDELEGATIONTOKEN, null, dToken, true, + doAsUser); return (Long) json.get(RENEW_DELEGATION_TOKEN_JSON); } @@ -187,9 +227,27 @@ public void cancelDelegationToken(URL url, AuthenticatedURL.Token token, Token dToken) throws IOException { + cancelDelegationToken(url, token, dToken, null); + } + + /** + * Cancels a delegation token from the server end-point. It does not require + * being authenticated by the configured Authenticator. + * + * @param url the URL to cancel the delegation token from. Only HTTP/S URLs + * are supported. + * @param token the authentication token with the Delegation Token to cancel. + * @param doAsUser the user to do as, which will be the token owner. + * @throws IOException if an IO error occurred. + */ + public void cancelDelegationToken(URL url, + AuthenticatedURL.Token token, + Token dToken, String doAsUser) + throws IOException { try { doDelegationTokenOperation(url, token, - DelegationTokenOperation.CANCELDELEGATIONTOKEN, null, dToken, false); + DelegationTokenOperation.CANCELDELEGATIONTOKEN, null, dToken, false, + doAsUser); } catch (AuthenticationException ex) { throw new IOException("This should not happen: " + ex.getMessage(), ex); } @@ -197,7 +255,7 @@ public void cancelDelegationToken(URL url, private Map doDelegationTokenOperation(URL url, AuthenticatedURL.Token token, DelegationTokenOperation operation, - String renewer, Token dToken, boolean hasResponse) + String renewer, Token dToken, boolean hasResponse, String doAsUser) throws IOException, AuthenticationException { Map ret = null; Map params = new HashMap(); @@ -208,6 +266,11 @@ private Map doDelegationTokenOperation(URL url, if (dToken != null) { params.put(TOKEN_PARAM, dToken.encodeToUrlString()); } + // proxyuser + if (doAsUser != null) { + params.put(DelegationTokenAuthenticatedURL.DO_AS, + URLEncoder.encode(doAsUser, "UTF-8")); + } String urlStr = url.toExternalForm(); StringBuilder sb = new StringBuilder(urlStr); String separator = (urlStr.contains("?")) ? "&" : "?"; diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java index 189a334ce2..87c3105ec2 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java @@ -52,6 +52,9 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.Writer; @@ -648,6 +651,16 @@ protected Properties getConfiguration(String configPrefix, "token-kind"); return conf; } + + @Override + protected org.apache.hadoop.conf.Configuration getProxyuserConfiguration( + FilterConfig filterConfig) throws ServletException { + org.apache.hadoop.conf.Configuration conf = + new org.apache.hadoop.conf.Configuration(false); + conf.set("proxyuser.client.users", OK_USER); + conf.set("proxyuser.client.hosts", "localhost"); + return conf; + } } private static class KerberosConfiguration extends Configuration { @@ -713,6 +726,19 @@ public T run() throws Exception { @Test public void testKerberosDelegationTokenAuthenticator() throws Exception { + testKerberosDelegationTokenAuthenticator(false); + } + + @Test + public void testKerberosDelegationTokenAuthenticatorWithDoAs() + throws Exception { + testKerberosDelegationTokenAuthenticator(true); + } + + private void testKerberosDelegationTokenAuthenticator( + final boolean doAs) throws Exception { + final String doAsUser = doAs ? OK_USER : null; + // setting hadoop security to kerberos org.apache.hadoop.conf.Configuration conf = new org.apache.hadoop.conf.Configuration(); @@ -742,7 +768,7 @@ public void testKerberosDelegationTokenAuthenticator() throws Exception { final URL url = new URL(getJettyURL() + "/foo/bar"); try { - aUrl.getDelegationToken(url, token, FOO_USER); + aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); Assert.fail(); } catch (AuthenticationException ex) { Assert.assertTrue(ex.getMessage().contains("GSSException")); @@ -752,25 +778,41 @@ public void testKerberosDelegationTokenAuthenticator() throws Exception { new Callable() { @Override public Void call() throws Exception { - aUrl.getDelegationToken(url, token, "client"); + aUrl.getDelegationToken( + url, token, doAs ? doAsUser : "client", doAsUser); + Assert.assertNotNull(token.getDelegationToken()); + Assert.assertEquals(new Text("token-kind"), + token.getDelegationToken().getKind()); + // Make sure the token belongs to the right owner + ByteArrayInputStream buf = new ByteArrayInputStream( + token.getDelegationToken().getIdentifier()); + DataInputStream dis = new DataInputStream(buf); + DelegationTokenIdentifier id = + new DelegationTokenIdentifier(new Text("token-kind")); + id.readFields(dis); + dis.close(); + Assert.assertEquals( + doAs ? new Text(OK_USER) : new Text("client"), id.getOwner()); + if (doAs) { + Assert.assertEquals(new Text("client"), id.getRealUser()); + } + + aUrl.renewDelegationToken(url, token, doAsUser); Assert.assertNotNull(token.getDelegationToken()); - aUrl.renewDelegationToken(url, token); - Assert.assertNotNull(token.getDelegationToken()); - - aUrl.getDelegationToken(url, token, FOO_USER); + aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); Assert.assertNotNull(token.getDelegationToken()); try { - aUrl.renewDelegationToken(url, token); + aUrl.renewDelegationToken(url, token, doAsUser); Assert.fail(); } catch (Exception ex) { Assert.assertTrue(ex.getMessage().contains("403")); } - aUrl.getDelegationToken(url, token, FOO_USER); + aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); - aUrl.cancelDelegationToken(url, token); + aUrl.cancelDelegationToken(url, token, doAsUser); Assert.assertNull(token.getDelegationToken()); return null;