From b802476d9d44e581d64f810c55fc8f0c35171401 Mon Sep 17 00:00:00 2001 From: Boris Shkolnik Date: Tue, 13 Apr 2010 23:01:43 +0000 Subject: [PATCH] HADOOP-6580. UGI should contain authentication method. git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@933810 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 2 + src/java/org/apache/hadoop/ipc/Server.java | 39 +++++++++----- .../apache/hadoop/security/SaslRpcServer.java | 12 +++-- src/java/org/apache/hadoop/security/User.java | 18 ++++++- .../hadoop/security/UserGroupInformation.java | 34 ++++++++++++ .../org/apache/hadoop/ipc/TestSaslRPC.java | 45 ++++++++++++++++ .../security/TestUserGroupInformation.java | 53 ++++++++++++++++++- 7 files changed, 184 insertions(+), 19 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0b0264be6b..2056f9de78 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -68,6 +68,8 @@ Trunk (unreleased changes) HADOOP-6586. Log authentication and authorization failures and successes for RPC (boryas) + HADOOP-6580. UGI should contain authentication method. (jnp via boryas) + IMPROVEMENTS HADOOP-6283. Improve the exception messages thrown by diff --git a/src/java/org/apache/hadoop/ipc/Server.java b/src/java/org/apache/hadoop/ipc/Server.java index 206f0b2431..e69d079e2f 100644 --- a/src/java/org/apache/hadoop/ipc/Server.java +++ b/src/java/org/apache/hadoop/ipc/Server.java @@ -72,6 +72,7 @@ import org.apache.hadoop.security.SaslRpcServer.SaslStatus; import org.apache.hadoop.security.SaslRpcServer.SaslDigestCallbackHandler; import org.apache.hadoop.security.SaslRpcServer.SaslGssCallbackHandler; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.hadoop.security.authorize.AuthorizationException; @@ -1084,18 +1085,32 @@ private void processHeader(byte[] buf) throws IOException { UserGroupInformation protocolUser = header.getUgi(); if (!useSasl) { user = protocolUser; - } else if ((protocolUser != null) - && (!protocolUser.getUserName().equals(user.getUserName()))) { - if (authMethod == AuthMethod.DIGEST) { - // Not allowed to doAs if token authentication is used - throw new AccessControlException("Authenticated user (" + user - + ") doesn't match what the client claims to be (" + protocolUser - + ")"); - } else { - //Effective user can be different from authenticated user - //for simple auth or kerberos auth - user = UserGroupInformation.createProxyUser(protocolUser - .getUserName(), user); + if (user != null) { + user.setAuthenticationMethod(AuthMethod.SIMPLE.authenticationMethod); + } + } else { + // user is authenticated + user.setAuthenticationMethod(authMethod.authenticationMethod); + //Now we check if this is a proxy user case. If the protocol user is + //different from the 'user', it is a proxy user scenario. However, + //this is not allowed if user authenticated with DIGEST. + if ((protocolUser != null) + && (!protocolUser.getUserName().equals(user.getUserName()))) { + if (authMethod == AuthMethod.DIGEST) { + // Not allowed to doAs if token authentication is used + throw new AccessControlException("Authenticated user (" + user + + ") doesn't match what the client claims to be (" + + protocolUser + ")"); + } else { + // Effective user can be different from authenticated user + // for simple auth or kerberos auth + // The user is the real user. Now we create a proxy user + UserGroupInformation realUser = user; + user = UserGroupInformation.createProxyUser(protocolUser + .getUserName(), realUser); + // Now the user is a proxy user, set Authentication method Proxy. + user.setAuthenticationMethod(AuthenticationMethod.PROXY); + } } } } diff --git a/src/java/org/apache/hadoop/security/SaslRpcServer.java b/src/java/org/apache/hadoop/security/SaslRpcServer.java index 16a7edbc3d..3928f84838 100644 --- a/src/java/org/apache/hadoop/security/SaslRpcServer.java +++ b/src/java/org/apache/hadoop/security/SaslRpcServer.java @@ -41,6 +41,7 @@ import org.apache.hadoop.ipc.Server; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.SecretManager.InvalidToken; /** @@ -102,17 +103,20 @@ private SaslStatus(int state) { /** Authentication method */ public static enum AuthMethod { - SIMPLE((byte) 80, ""), // no authentication - KERBEROS((byte) 81, "GSSAPI"), // SASL Kerberos authentication - DIGEST((byte) 82, "DIGEST-MD5"); // SASL DIGEST-MD5 authentication + SIMPLE((byte) 80, "", AuthenticationMethod.SIMPLE), + KERBEROS((byte) 81, "GSSAPI", AuthenticationMethod.KERBEROS), + DIGEST((byte) 82, "DIGEST-MD5", AuthenticationMethod.TOKEN); /** The code for this method. */ public final byte code; public final String mechanismName; + public final AuthenticationMethod authenticationMethod; - private AuthMethod(byte code, String mechanismName) { + private AuthMethod(byte code, String mechanismName, + AuthenticationMethod authMethod) { this.code = code; this.mechanismName = mechanismName; + this.authenticationMethod = authMethod; } private static final int FIRST_CODE = values()[0].code; diff --git a/src/java/org/apache/hadoop/security/User.java b/src/java/org/apache/hadoop/security/User.java index 6a54311572..2918209d36 100644 --- a/src/java/org/apache/hadoop/security/User.java +++ b/src/java/org/apache/hadoop/security/User.java @@ -19,6 +19,8 @@ import java.security.Principal; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; + /** * Save the full and short name of the user as a principal. This allows us to * have a single type that we always look for when picking up user names. @@ -26,8 +28,13 @@ class User implements Principal { private final String fullName; private final String shortName; + private AuthenticationMethod authMethod = null; public User(String name) { + this(name, null); + } + + public User(String name, AuthenticationMethod authMethod) { fullName = name; int atIdx = name.indexOf('@'); if (atIdx == -1) { @@ -40,6 +47,7 @@ public User(String name) { shortName = name.substring(0, slashIdx); } } + this.authMethod = authMethod; } /** @@ -65,7 +73,7 @@ public boolean equals(Object o) { } else if (o == null || getClass() != o.getClass()) { return false; } else { - return fullName.equals(((User) o).fullName); + return ((fullName.equals(((User) o).fullName)) && (authMethod == ((User) o).authMethod)); } } @@ -78,4 +86,12 @@ public int hashCode() { public String toString() { return fullName; } + + public void setAuthenticationMethod(AuthenticationMethod authMethod) { + this.authMethod = authMethod; + } + + public AuthenticationMethod getAuthenticationMethod() { + return authMethod; + } } diff --git a/src/java/org/apache/hadoop/security/UserGroupInformation.java b/src/java/org/apache/hadoop/security/UserGroupInformation.java index 1d49853228..3b717dafc0 100644 --- a/src/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/src/java/org/apache/hadoop/security/UserGroupInformation.java @@ -51,6 +51,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.SaslRpcServer.AuthMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -470,6 +471,15 @@ public static UserGroupInformation createRemoteUser(String user) { return new UserGroupInformation(subject); } + public static enum AuthenticationMethod { + SIMPLE, + KERBEROS, + TOKEN, + CERTIFICATE, + KERBEROS_SSL, + PROXY; + } + /* Create a proxy user using username of the effective user and the ugi of the * real user. * @@ -649,6 +659,30 @@ public String toString() { } } + /** + * Sets the authentication method in the subject + * + * @param authMethod + */ + public synchronized + void setAuthenticationMethod(AuthenticationMethod authMethod) { + for (User p : subject.getPrincipals(User.class)) { + p.setAuthenticationMethod(authMethod); + } + } + + /** + * Get the authentication method from the subject + * + * @return AuthenticationMethod in the subject, null if not present. + */ + public synchronized AuthenticationMethod getAuthenticationMethod() { + for (User p: subject.getPrincipals(User.class)) { + return p.getAuthenticationMethod(); + } + return null; + } + /** * Compare the subjects to see if they are equal to each other. */ diff --git a/src/test/core/org/apache/hadoop/ipc/TestSaslRPC.java b/src/test/core/org/apache/hadoop/ipc/TestSaslRPC.java index 4ea5415cba..3e9fea3280 100644 --- a/src/test/core/org/apache/hadoop/ipc/TestSaslRPC.java +++ b/src/test/core/org/apache/hadoop/ipc/TestSaslRPC.java @@ -25,8 +25,11 @@ import java.io.DataOutput; import java.io.IOException; import java.net.InetSocketAddress; +import java.security.PrivilegedExceptionAction; import java.util.Collection; +import junit.framework.Assert; + import org.apache.commons.logging.*; import org.apache.commons.logging.impl.Log4JLogger; @@ -44,6 +47,7 @@ import org.apache.hadoop.security.SaslRpcClient; import org.apache.hadoop.security.SaslRpcServer; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.log4j.Level; import org.junit.Test; @@ -161,10 +165,14 @@ public Token selectToken(Text service, @KerberosInfo(SERVER_PRINCIPAL_KEY) @TokenInfo(TestTokenSelector.class) public interface TestSaslProtocol extends TestRPC.TestProtocol { + public AuthenticationMethod getAuthMethod() throws IOException; } public static class TestSaslImpl extends TestRPC.TestImpl implements TestSaslProtocol { + public AuthenticationMethod getAuthMethod() throws IOException { + return UserGroupInformation.getCurrentUser().getAuthenticationMethod(); + } } @Test @@ -258,6 +266,43 @@ static void testKerberosRpc(String principal, String keytab) throws Exception { } } + @Test + public void testDigestAuthMethod() throws Exception { + TestTokenSecretManager sm = new TestTokenSecretManager(); + Server server = RPC.getServer(TestSaslProtocol.class, + new TestSaslImpl(), ADDRESS, 0, 5, true, conf, sm); + server.start(); + + final UserGroupInformation current = UserGroupInformation.getCurrentUser(); + final InetSocketAddress addr = NetUtils.getConnectAddress(server); + TestTokenIdentifier tokenId = new TestTokenIdentifier(new Text(current + .getUserName())); + Token token = new Token(tokenId, + sm); + Text host = new Text(addr.getAddress().getHostAddress() + ":" + + addr.getPort()); + token.setService(host); + LOG.info("Service IP address for token is " + host); + current.addToken(token); + + current.doAs(new PrivilegedExceptionAction() { + public Object run() throws IOException { + TestSaslProtocol proxy = null; + try { + proxy = (TestSaslProtocol) RPC.getProxy(TestSaslProtocol.class, + TestSaslProtocol.versionID, addr, conf); + Assert.assertEquals(AuthenticationMethod.TOKEN, proxy.getAuthMethod()); + } finally { + if (proxy != null) { + RPC.stopProxy(proxy); + } + } + return null; + } + }); + server.stop(); + } + public static void main(String[] args) throws Exception { System.out.println("Testing Kerberos authentication over RPC"); if (args.length != 2) { diff --git a/src/test/core/org/apache/hadoop/security/TestUserGroupInformation.java b/src/test/core/org/apache/hadoop/security/TestUserGroupInformation.java index 1bbd04a387..f8f379c53e 100644 --- a/src/test/core/org/apache/hadoop/security/TestUserGroupInformation.java +++ b/src/test/core/org/apache/hadoop/security/TestUserGroupInformation.java @@ -27,13 +27,15 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.apache.hadoop.conf.Configuration; +import junit.framework.Assert; + +import org.apache.hadoop.security.SaslRpcServer.AuthMethod; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.junit.Test; @@ -212,4 +214,51 @@ public Collection> run() throws IOException { assertTrue(otherSet.contains(t1)); assertTrue(otherSet.contains(t2)); } + + @Test + public void testUGIAuthMethod() throws Exception { + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + final AuthenticationMethod am = AuthenticationMethod.KERBEROS; + ugi.setAuthenticationMethod(am); + Assert.assertEquals(am, ugi.getAuthenticationMethod()); + ugi.doAs(new PrivilegedExceptionAction() { + public Object run() throws IOException { + Assert.assertEquals(am, UserGroupInformation.getCurrentUser() + .getAuthenticationMethod()); + return null; + } + }); + } + + @Test + public void testUGIAuthMethodInRealUser() throws Exception { + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation proxyUgi = UserGroupInformation.createProxyUser( + "proxy", ugi); + final AuthenticationMethod am = AuthenticationMethod.KERBEROS; + ugi.setAuthenticationMethod(am); + Assert.assertEquals(am, ugi.getAuthenticationMethod()); + Assert.assertEquals(null, proxyUgi.getAuthenticationMethod()); + proxyUgi.setAuthenticationMethod(AuthenticationMethod.PROXY); + proxyUgi.doAs(new PrivilegedExceptionAction() { + public Object run() throws IOException { + Assert.assertEquals(AuthenticationMethod.PROXY, UserGroupInformation + .getCurrentUser().getAuthenticationMethod()); + Assert.assertEquals(am, UserGroupInformation.getCurrentUser() + .getRealUser().getAuthenticationMethod()); + return null; + } + }); + UserGroupInformation proxyUgi2 = UserGroupInformation.createProxyUser( + "proxy", ugi); + proxyUgi2.setAuthenticationMethod(AuthenticationMethod.PROXY); + Assert.assertEquals(proxyUgi, proxyUgi2); + // Equality should work if authMethod is null + UserGroupInformation realugi = UserGroupInformation.getCurrentUser(); + UserGroupInformation proxyUgi3 = UserGroupInformation.createProxyUser( + "proxyAnother", realugi); + UserGroupInformation proxyUgi4 = UserGroupInformation.createProxyUser( + "proxyAnother", realugi); + Assert.assertEquals(proxyUgi3, proxyUgi4); + } }