From 86ce5f6c917131e79174f8c7ac55d6cb1abad09d Mon Sep 17 00:00:00 2001 From: Robert Joseph Evans Date: Thu, 15 Nov 2012 21:14:37 +0000 Subject: [PATCH] HADOOP-9035. Generalize setup of LoginContext (daryn via bobby) git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1410018 13f79535-47bb-0310-9956-ffa450edef68 --- .../hadoop-common/CHANGES.txt | 2 + .../java/org/apache/hadoop/ipc/Client.java | 13 +-- .../hadoop/security/UserGroupInformation.java | 91 ++++++++++++------- .../security/TestUserGroupInformation.java | 63 ++++++++++++- 4 files changed, 127 insertions(+), 42 deletions(-) diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 5689ae0d848..fc3c8fed55e 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -369,6 +369,8 @@ Release 2.0.3-alpha - Unreleased HADOO-8998. set Cache-Control no-cache header on all dynamic content. (tucu) + HADOOP-9035. Generalize setup of LoginContext (daryn via bobby) + OPTIMIZATIONS HADOOP-8866. SampleQuantiles#query is O(N^2) instead of O(N). (Andrew Wang diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java index 1af14f941c7..9bea3db4ef1 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java @@ -294,14 +294,15 @@ public class Client { } } + AuthenticationMethod authentication; if (token != null) { - authMethod = AuthenticationMethod.TOKEN.getAuthMethod(); - } else if (UserGroupInformation.isSecurityEnabled()) { - // eventually just use the ticket's authMethod - authMethod = AuthMethod.KERBEROS; - } else { - authMethod = AuthMethod.SIMPLE; + authentication = AuthenticationMethod.TOKEN; + } else if (ticket != null) { + authentication = ticket.getRealAuthenticationMethod(); + } else { // this only happens in lazy tests + authentication = AuthenticationMethod.SIMPLE; } + authMethod = authentication.getAuthMethod(); if (LOG.isDebugEnabled()) LOG.debug("Use " + authMethod + " authentication for protocol " diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java index bc82decd622..d206f3ace86 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.security; -import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN_DEFAULT; @@ -133,7 +132,7 @@ public class UserGroupInformation { } Principal user = null; // if we are using kerberos, try it out - if (useKerberos) { + if (isAuthenticationMethodEnabled(AuthenticationMethod.KERBEROS)) { user = getCanonicalUser(KerberosPrincipal.class); if (LOG.isDebugEnabled()) { LOG.debug("using kerberos user:"+user); @@ -191,8 +190,8 @@ public class UserGroupInformation { static UgiMetrics metrics = UgiMetrics.create(); /** Are the static variables that depend on configuration initialized? */ private static boolean isInitialized = false; - /** Should we use Kerberos configuration? */ - private static boolean useKerberos; + /** The auth method to use */ + private static AuthenticationMethod authenticationMethod; /** Server-side groups fetching service */ private static Groups groups; /** Min time (in seconds) before relogin for Kerberos */ @@ -237,20 +236,7 @@ public class UserGroupInformation { * @param conf the configuration to use */ private static synchronized void initUGI(Configuration conf) { - AuthenticationMethod auth = SecurityUtil.getAuthenticationMethod(conf); - switch (auth) { - case SIMPLE: - case TOKEN: - useKerberos = false; - break; - case KERBEROS: - useKerberos = true; - break; - default: - throw new IllegalArgumentException("Invalid attribute value for " + - HADOOP_SECURITY_AUTHENTICATION + - " of " + auth); - } + authenticationMethod = SecurityUtil.getAuthenticationMethod(conf); try { kerberosMinSecondsBeforeRelogin = 1000L * conf.getLong( HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN, @@ -288,8 +274,14 @@ public class UserGroupInformation { * @return true if UGI is working in a secure environment */ public static boolean isSecurityEnabled() { + return !isAuthenticationMethodEnabled(AuthenticationMethod.SIMPLE); + } + + @InterfaceAudience.Private + @InterfaceStability.Evolving + private static boolean isAuthenticationMethodEnabled(AuthenticationMethod method) { ensureInitialized(); - return useKerberos; + return (authenticationMethod == method); } /** @@ -585,7 +577,7 @@ public class UserGroupInformation { @InterfaceStability.Evolving public static UserGroupInformation getUGIFromTicketCache( String ticketCache, String user) throws IOException { - if (!isSecurityEnabled()) { + if (!isAuthenticationMethodEnabled(AuthenticationMethod.KERBEROS)) { return getBestUGI(null, user); } try { @@ -638,19 +630,12 @@ public class UserGroupInformation { public synchronized static UserGroupInformation getLoginUser() throws IOException { if (loginUser == null) { + ensureInitialized(); try { Subject subject = new Subject(); - LoginContext login; - AuthenticationMethod authenticationMethod; - if (isSecurityEnabled()) { - authenticationMethod = AuthenticationMethod.KERBEROS; - login = newLoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME, - subject, new HadoopConfiguration()); - } else { - authenticationMethod = AuthenticationMethod.SIMPLE; - login = newLoginContext(HadoopConfiguration.SIMPLE_CONFIG_NAME, - subject, new HadoopConfiguration()); - } + LoginContext login = + newLoginContext(authenticationMethod.getLoginAppName(), + subject, new HadoopConfiguration()); login.login(); loginUser = new UserGroupInformation(subject); loginUser.setLogin(login); @@ -675,6 +660,14 @@ public class UserGroupInformation { return loginUser; } + @InterfaceAudience.Private + @InterfaceStability.Unstable + synchronized static void setLoginUser(UserGroupInformation ugi) { + // if this is to become stable, should probably logout the currently + // logged in ugi if it's different + loginUser = ugi; + } + /** * Is this user logged in from a keytab file? * @return true if the credentials are from a keytab file. @@ -1027,22 +1020,38 @@ public class UserGroupInformation { public static enum AuthenticationMethod { // currently we support only one auth per method, but eventually a // subtype is needed to differentiate, ex. if digest is token or ldap - SIMPLE(AuthMethod.SIMPLE), - KERBEROS(AuthMethod.KERBEROS), + SIMPLE(AuthMethod.SIMPLE, + HadoopConfiguration.SIMPLE_CONFIG_NAME), + KERBEROS(AuthMethod.KERBEROS, + HadoopConfiguration.USER_KERBEROS_CONFIG_NAME), TOKEN(AuthMethod.DIGEST), CERTIFICATE(null), KERBEROS_SSL(null), PROXY(null); private final AuthMethod authMethod; + private final String loginAppName; + private AuthenticationMethod(AuthMethod authMethod) { + this(authMethod, null); + } + private AuthenticationMethod(AuthMethod authMethod, String loginAppName) { this.authMethod = authMethod; + this.loginAppName = loginAppName; } public AuthMethod getAuthMethod() { return authMethod; } + String getLoginAppName() { + if (loginAppName == null) { + throw new UnsupportedOperationException( + this + " login authentication is not supported"); + } + return loginAppName; + } + public static AuthenticationMethod valueOf(AuthMethod authMethod) { for (AuthenticationMethod value : values()) { if (value.getAuthMethod() == authMethod) { @@ -1334,7 +1343,21 @@ public class UserGroupInformation { public synchronized AuthenticationMethod getAuthenticationMethod() { return user.getAuthenticationMethod(); } - + + /** + * Get the authentication method from the real user's subject. If there + * is no real user, return the given user's authentication method. + * + * @return AuthenticationMethod in the subject, null if not present. + */ + public synchronized AuthenticationMethod getRealAuthenticationMethod() { + UserGroupInformation ugi = getRealUser(); + if (ugi == null) { + ugi = this; + } + return ugi.getAuthenticationMethod(); + } + /** * Returns the authentication method of a ugi. If the authentication method is * PROXY, returns the authentication method of the real user. diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java index 2acbceffd2d..414aeb958dc 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java @@ -70,16 +70,75 @@ public class TestUserGroupInformation { /** configure ugi */ @BeforeClass public static void setup() { + javax.security.auth.login.Configuration.setConfiguration( + new DummyLoginConfiguration()); + } + + @Before + public void setupUgi() { conf = new Configuration(); conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL, "RULE:[2:$1@$0](.*@HADOOP.APACHE.ORG)s/@.*//" + "RULE:[1:$1@$0](.*@HADOOP.APACHE.ORG)s/@.*//" + "DEFAULT"); UserGroupInformation.setConfiguration(conf); - javax.security.auth.login.Configuration.setConfiguration( - new DummyLoginConfiguration()); + UserGroupInformation.setLoginUser(null); } + @After + public void resetUgi() { + UserGroupInformation.setLoginUser(null); + } + + @Test + public void testSimpleLogin() throws IOException { + tryLoginAuthenticationMethod(AuthenticationMethod.SIMPLE, true); + } + + @Test + public void testTokenLogin() throws IOException { + tryLoginAuthenticationMethod(AuthenticationMethod.TOKEN, false); + } + + @Test + public void testProxyLogin() throws IOException { + tryLoginAuthenticationMethod(AuthenticationMethod.PROXY, false); + } + + private void tryLoginAuthenticationMethod(AuthenticationMethod method, + boolean expectSuccess) + throws IOException { + SecurityUtil.setAuthenticationMethod(method, conf); + UserGroupInformation.setConfiguration(conf); // pick up changed auth + + UserGroupInformation ugi = null; + Exception ex = null; + try { + ugi = UserGroupInformation.getLoginUser(); + } catch (Exception e) { + ex = e; + } + if (expectSuccess) { + assertNotNull(ugi); + assertEquals(method, ugi.getAuthenticationMethod()); + } else { + assertNotNull(ex); + assertEquals(UnsupportedOperationException.class, ex.getClass()); + assertEquals(method + " login authentication is not supported", + ex.getMessage()); + } + } + + @Test + public void testGetRealAuthenticationMethod() { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("user1"); + ugi.setAuthenticationMethod(AuthenticationMethod.SIMPLE); + assertEquals(AuthenticationMethod.SIMPLE, ugi.getAuthenticationMethod()); + assertEquals(AuthenticationMethod.SIMPLE, ugi.getRealAuthenticationMethod()); + ugi = UserGroupInformation.createProxyUser("user2", ugi); + assertEquals(AuthenticationMethod.PROXY, ugi.getAuthenticationMethod()); + assertEquals(AuthenticationMethod.SIMPLE, ugi.getRealAuthenticationMethod()); + } /** Test login method */ @Test public void testLogin() throws Exception {