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.security; 019 020import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS; 021import static org.apache.hadoop.util.PlatformName.IBM_JAVA; 022 023import com.sun.security.auth.login.ConfigFile; 024 025import java.io.File; 026import java.io.IOException; 027import java.lang.reflect.UndeclaredThrowableException; 028import java.net.URL; 029import java.security.AccessControlContext; 030import java.security.AccessController; 031import java.security.Principal; 032import java.security.PrivilegedAction; 033import java.security.PrivilegedActionException; 034import java.security.PrivilegedExceptionAction; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collection; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.Iterator; 041import java.util.LinkedHashSet; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045 046import javax.security.auth.Subject; 047import javax.security.auth.kerberos.KerberosKey; 048import javax.security.auth.kerberos.KerberosPrincipal; 049import javax.security.auth.kerberos.KerberosTicket; 050import javax.security.auth.login.AppConfigurationEntry; 051import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; 052import javax.security.auth.login.LoginContext; 053import javax.security.auth.login.LoginException; 054 055import org.apache.commons.logging.Log; 056import org.apache.commons.logging.LogFactory; 057import org.apache.hadoop.classification.InterfaceAudience; 058import org.apache.hadoop.classification.InterfaceStability; 059import org.apache.hadoop.classification.MapRModified; 060import org.apache.hadoop.conf.Configuration; 061import org.apache.hadoop.fs.CommonConfigurationKeysPublic; 062import org.apache.hadoop.io.Text; 063import org.apache.hadoop.metrics2.annotation.Metric; 064import org.apache.hadoop.metrics2.annotation.Metrics; 065import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem; 066import org.apache.hadoop.metrics2.lib.MetricsRegistry; 067import org.apache.hadoop.metrics2.lib.MutableQuantiles; 068import org.apache.hadoop.metrics2.lib.MutableRate; 069import org.apache.hadoop.security.SaslRpcServer.AuthMethod; 070import org.apache.hadoop.security.login.HadoopLoginModule; 071import org.apache.hadoop.security.rpcauth.RpcAuthMethod; 072import org.apache.hadoop.security.rpcauth.RpcAuthRegistry; 073import org.apache.hadoop.security.token.Token; 074import org.apache.hadoop.security.token.TokenIdentifier; 075import org.apache.hadoop.util.Shell; 076import org.apache.hadoop.util.Time; 077 078 079import com.google.common.annotations.VisibleForTesting; 080 081/** 082 * User and group information for Hadoop. 083 * This class wraps around a JAAS Subject and provides methods to determine the 084 * user's username and groups. It supports both the Windows, Unix and Kerberos 085 * login modules. 086 */ 087@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "HBase", "Hive", "Oozie"}) 088@InterfaceStability.Evolving 089@MapRModified 090public class UserGroupInformation { 091 private static final Log LOG = LogFactory.getLog(UserGroupInformation.class); 092 /** 093 * Percentage of the ticket window to use before we renew ticket. 094 */ 095 private static final float TICKET_RENEW_WINDOW = 0.80f; 096 static final String HADOOP_USER_NAME = "HADOOP_USER_NAME"; 097 static final String HADOOP_PROXY_USER = "HADOOP_PROXY_USER"; 098 099 // Begin MapR section 100 private static final String HADOOP_SECURITY_SPOOF_USER = "hadoop.spoof.user"; 101 private static final String HADOOP_SECURITY_SPOOFED_USER = "hadoop.spoofed.user.username"; 102 private static boolean spoofUser = false; 103 private static String spoofedUser; 104 105 private static void checkSpoofing(Configuration conf) { 106 // spoof users by default on Windows 107 spoofUser = conf.getBoolean(HADOOP_SECURITY_SPOOF_USER, 108 windows ? true : false); 109 110 if (!spoofUser) { 111 return; 112 } 113 114 spoofedUser = conf.get(HADOOP_SECURITY_SPOOFED_USER, "root"); 115 } 116 // End MapR section 117 118 /** 119 * UgiMetrics maintains UGI activity statistics 120 * and publishes them through the metrics interfaces. 121 */ 122 @Metrics(about="User and group related metrics", context="ugi") 123 static class UgiMetrics { 124 final MetricsRegistry registry = new MetricsRegistry("UgiMetrics"); 125 126 @Metric("Rate of successful kerberos logins and latency (milliseconds)") 127 MutableRate loginSuccess; 128 @Metric("Rate of failed kerberos logins and latency (milliseconds)") 129 MutableRate loginFailure; 130 @Metric("GetGroups") MutableRate getGroups; 131 MutableQuantiles[] getGroupsQuantiles; 132 133 static UgiMetrics create() { 134 return DefaultMetricsSystem.instance().register(new UgiMetrics()); 135 } 136 137 void addGetGroups(long latency) { 138 getGroups.add(latency); 139 if (getGroupsQuantiles != null) { 140 for (MutableQuantiles q : getGroupsQuantiles) { 141 q.add(latency); 142 } 143 } 144 } 145 } 146 147 /** Metrics to track UGI activity */ 148 static UgiMetrics metrics = UgiMetrics.create(); 149 /** The auth method to use */ 150 private static AuthenticationMethod authenticationMethod; 151 /** Server-side groups fetching service */ 152 private static Groups groups; 153 /** The configuration to use */ 154 private static Configuration conf; 155 156 /** JAAS configuration name to be used for user login */ 157 private static String userJAASConfName; 158 159 /** JAAS configuration name to be used for service login */ 160 private static String serviceJAASConfName; 161 162 /** Leave 10 minutes between relogin attempts. */ 163 private static final long MIN_TIME_BEFORE_RELOGIN = 10 * 60 * 1000L; 164 165 /**Environment variable pointing to the token cache file*/ 166 public static final String HADOOP_TOKEN_FILE_LOCATION = 167 "HADOOP_TOKEN_FILE_LOCATION"; 168 169 private static Class<? extends Principal> customAuthPrincipalClass; 170 171 private static Class<? extends RpcAuthMethod> customRpcAuthMethodClass; 172 173 // mapr_extensibility 174 public static final String USER_TICKET_FILE_LOCATION = "MAPR_TICKETFILE_LOCATION"; 175 176 private static final String JAVA_SECURITY_AUTH_LOGIN_CONFIG = "java.security.auth.login.config"; 177 178 /** 179 * A method to initialize the fields that depend on a configuration. 180 * Must be called before useKerberos or groups is used. 181 */ 182 private static void ensureInitialized() { 183 if (conf == null) { 184 synchronized(UserGroupInformation.class) { 185 if (conf == null) { // someone might have beat us 186 initialize(new Configuration(), false); 187 } 188 } 189 } 190 } 191 192 /** 193 * Initialize UGI and related classes. 194 * @param conf the configuration to use 195 */ 196 private static synchronized void initialize(Configuration conf, 197 boolean overrideNameRules) { 198 authenticationMethod = SecurityUtil.getAuthenticationMethod(conf); 199 if (LOG.isDebugEnabled()) 200 LOG.debug("HADOOP_SECURITY_AUTHENTICATION is set to: " + authenticationMethod); 201 202 // spoofing is only allowed when insecure 203 if (authenticationMethod == null || authenticationMethod.equals(AuthenticationMethod.SIMPLE)) { 204 checkSpoofing(conf); 205 } else { 206 // Initialize custom auth method. 207 // TODO This code really belongs to RpcAuthRegistry. 208 customAuthPrincipalClass = SecurityUtil.getCustomAuthPrincipal(conf); 209 customRpcAuthMethodClass = SecurityUtil.getCustomRpcAuthMethod(conf); 210 } 211 212 String jaasConfName = null; 213 if (authenticationMethod == AuthenticationMethod.SIMPLE) { 214 jaasConfName = "simple"; 215 } else { 216 LOG.debug("Security is enabled."); 217 // ignore what is passed in and instead honor extra configuration 218 // or JVM setting if neither set, use default. 219 jaasConfName = System.getProperty("hadoop.login"); 220 if (jaasConfName == null) { 221 jaasConfName = conf.get("hadoop.login", "default"); 222 } 223 } 224 225 userJAASConfName = jaasConfName.startsWith("hadoop_") 226 ? jaasConfName : "hadoop_" + jaasConfName; 227 serviceJAASConfName = userJAASConfName.endsWith("_keytab") 228 ? userJAASConfName : userJAASConfName + "_keytab"; 229 230 if (LOG.isDebugEnabled()) 231 LOG.debug("Login configuration entry is " + userJAASConfName); 232 233 String loginConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG); 234 235 if (loginConfPath == null) { 236 loginConfPath = conf.get(JAVA_SECURITY_AUTH_LOGIN_CONFIG); 237 if (loginConfPath != null ) { 238 System.setProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG, loginConfPath); 239 loginConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG); 240 LOG.info("Java System property 'java.security.auth.login.config' not" 241 + " set, unilaterally setting to " + loginConfPath); 242 } 243 } 244 // still can be null 245 if ( loginConfPath == null || !(new File(loginConfPath)).canRead() ) { 246 if ( LOG.isDebugEnabled()) { 247 LOG.debug(loginConfPath + " either null or can not be read. Trying to load " 248 + "'java.security.auth.login.config' from jar"); 249 } 250 String javaSecurityJarPath = 251 conf.get(CommonConfigurationKeysPublic.HADOOP_SECURITY_JAVA_SECURITY_JAR_PATH); 252 URL javaSecurityURL = null; 253 if ( javaSecurityJarPath != null ) { 254 javaSecurityURL = UserGroupInformation.class.getResource(javaSecurityJarPath); 255 if ( javaSecurityURL != null ) { 256 loginConfPath = javaSecurityURL.toExternalForm(); 257 System.setProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG, loginConfPath); 258 if ( LOG.isDebugEnabled()) { 259 LOG.debug("Loading 'java.security.auth.login.config' from: " + loginConfPath); 260 } 261 } 262 } 263 if (javaSecurityJarPath == null || javaSecurityURL == null ) { 264 LOG.warn("'java.security.auth.login.config' is not" 265 + " configured either in Hadoop configuration or" 266 + " via Java property or not loaded from jar, may cause login failure"); 267 } 268 } 269 270 if (overrideNameRules || !HadoopKerberosName.hasRulesBeenSet()) { 271 try { 272 HadoopKerberosName.setConfiguration(conf); 273 } catch (IOException ioe) { 274 throw new RuntimeException( 275 "Problem with Kerberos auth_to_local name configuration", ioe); 276 } 277 } 278 // If we haven't set up testing groups, use the configuration to find it 279 if (!(groups instanceof TestingGroups)) { 280 groups = Groups.getUserToGroupsMappingService(conf); 281 } 282 UserGroupInformation.conf = conf; 283 284 if (metrics.getGroupsQuantiles == null) { 285 int[] intervals = conf.getInts(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS); 286 if (intervals != null && intervals.length > 0) { 287 final int length = intervals.length; 288 MutableQuantiles[] getGroupsQuantiles = new MutableQuantiles[length]; 289 for (int i = 0; i < length; i++) { 290 getGroupsQuantiles[i] = metrics.registry.newQuantiles( 291 "getGroups" + intervals[i] + "s", 292 "Get groups", "ops", "latency", intervals[i]); 293 } 294 metrics.getGroupsQuantiles = getGroupsQuantiles; 295 } 296 } 297 } 298 299 /** 300 * Set the static configuration for UGI. 301 * In particular, set the security authentication mechanism and the 302 * group look up service. 303 * @param conf the configuration to use 304 */ 305 @InterfaceAudience.Public 306 @InterfaceStability.Evolving 307 public static void setConfiguration(Configuration conf) { 308 initialize(conf, true); 309 } 310 311 @InterfaceAudience.Private 312 @VisibleForTesting 313 static void reset() { 314 authenticationMethod = null; 315 conf = null; 316 groups = null; 317 setLoginUser(null); 318 HadoopKerberosName.setRules(null); 319 } 320 321 /** 322 * Determine if UserGroupInformation is using Kerberos to determine 323 * user identities or is relying on simple authentication 324 * 325 * @return true if UGI is working in a secure environment 326 */ 327 public static boolean isSecurityEnabled() { 328 return !isAuthenticationMethodEnabled(AuthenticationMethod.SIMPLE); 329 } 330 331 @InterfaceAudience.Private 332 @InterfaceStability.Evolving 333 private static boolean isAuthenticationMethodEnabled(AuthenticationMethod method) { 334 ensureInitialized(); 335 return (authenticationMethod == method); 336 } 337 338 /** 339 * Information about the logged in user. 340 */ 341 private static UserGroupInformation loginUser = null; 342 private static String keytabPrincipal = null; 343 private static String keytabFile = null; 344 345 private final Subject subject; 346 // All non-static fields must be read-only caches that come from the subject. 347 private final User user; 348 private final boolean isKeytab; 349 private final boolean isKrbTkt; 350 private List<RpcAuthMethod> rpcAuthMethodList; 351 352 private static final boolean windows = 353 System.getProperty("os.name").startsWith("Windows"); 354 355 private static class RealUser implements Principal { 356 private final UserGroupInformation realUser; 357 358 RealUser(UserGroupInformation realUser) { 359 this.realUser = realUser; 360 } 361 362 @Override 363 public String getName() { 364 return realUser.getUserName(); 365 } 366 367 public UserGroupInformation getRealUser() { 368 return realUser; 369 } 370 371 @Override 372 public boolean equals(Object o) { 373 if (this == o) { 374 return true; 375 } else if (o == null || getClass() != o.getClass()) { 376 return false; 377 } else { 378 return realUser.equals(((RealUser) o).realUser); 379 } 380 } 381 382 @Override 383 public int hashCode() { 384 return realUser.hashCode(); 385 } 386 387 @Override 388 public String toString() { 389 return realUser.toString(); 390 } 391 } 392 393 private static LoginContext 394 newLoginContext(String appName, Subject subject, 395 Map<String,?> overrideOptions) 396 throws LoginException { 397 // Temporarily switch the thread's ContextClassLoader to match this 398 // class's classloader, so that we can properly load HadoopLoginModule 399 // from the JAAS libraries. 400 Thread t = Thread.currentThread(); 401 ClassLoader oldCCL = t.getContextClassLoader(); 402 t.setContextClassLoader(HadoopLoginModule.class.getClassLoader()); 403 try { 404 if (overrideOptions != null) { 405 javax.security.auth.login.Configuration cfg = 406 new DynamicLoginConfiguration(getJAASConf(), overrideOptions); 407 return new LoginContext(appName, subject, null, cfg); 408 } 409 return new LoginContext(appName, subject); 410 } finally { 411 t.setContextClassLoader(oldCCL); 412 } 413 } 414 415 private static LoginContext newLoginContextWithKeyTab(String appName, 416 Subject subject, String keytab, String principal) throws LoginException { 417 Map<String,String> overrideOptions = new HashMap<String, String>(); 418 overrideOptions.put("keyTab", keytab); 419 overrideOptions.put("principal", principal); 420 return newLoginContext(appName, subject, overrideOptions); 421 } 422 423 private static javax.security.auth.login.Configuration getJAASConf() { 424 return javax.security.auth.login.Configuration.getConfiguration(); 425 } 426 427 private LoginContext getLogin() { 428 return user.getLogin(); 429 } 430 431 private void setLogin(LoginContext login) { 432 user.setLogin(login); 433 } 434 435 private void configureRpcAuthMethods(String confName) { 436 ArrayList<RpcAuthMethod> authMethodList = new ArrayList<RpcAuthMethod>(); 437 if (isSecurityEnabled() && confName != null) { 438 Set<RpcAuthMethod> authMethods = new LinkedHashSet<RpcAuthMethod>(); 439 AppConfigurationEntry[] appInfo = 440 getJAASConf().getAppConfigurationEntry(confName); 441 for (int i = 0; appInfo != null && i < appInfo.length; i++) { 442 String module = appInfo[i].getLoginModuleName(); 443 RpcAuthMethod rpcAuthMethod = RpcAuthRegistry.getAuthMethodForLoginModule(module); 444 if (rpcAuthMethod != null) { 445 authMethods.add(rpcAuthMethod); 446 } 447 } 448 authMethods.add(RpcAuthRegistry.getAuthMethod(authenticationMethod)); 449 450 if (isSecurityEnabled() 451 && !"hadoop_simple".equals(confName) 452 && authMethods.size() == 0) { 453 LOG.warn("Security is enabled but no suitable RPC authentication " 454 + "method is found in the provided JAAS configuration: " + confName); 455 } 456 authMethodList.addAll(authMethods); 457 } else { 458 authMethodList.add(RpcAuthRegistry.SIMPLE); 459 } 460 461 this.rpcAuthMethodList = Collections.unmodifiableList(authMethodList); 462 } 463 464 /** 465 * Create a UserGroupInformation for the given subject. 466 * This does not change the subject or acquire new credentials. 467 * @param subject the user's subject 468 */ 469 UserGroupInformation(Subject subject) { 470 this(subject, userJAASConfName); 471 } 472 473 UserGroupInformation(Subject subject, String loginConfName) { 474 this.subject = subject; 475 this.user = subject.getPrincipals(User.class).iterator().next(); 476 this.isKeytab = !subject.getPrivateCredentials(KerberosKey.class).isEmpty(); 477 this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty(); 478 configureRpcAuthMethods(loginConfName); 479 480 /* Since multiple methods are allowed at once, this isn't a great setting. 481 * We just prefer the dominant method. This concept really should be removed. 482 * Better to have the login modules set the methods of authentication based 483 * upon some common mapping or even callers just look directly at the subject 484 * to determine what is in it. 485 */ 486 if (!subject.getPrincipals(KerberosPrincipal.class).isEmpty()) { 487 this.user.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 488 LOG.debug("found Kerberos Principal in subject, marking as such"); 489 } else if (customAuthPrincipalClass != null && !subject.getPrincipals(customAuthPrincipalClass).isEmpty()) { 490 this.user.setAuthenticationMethod(AuthenticationMethod.CUSTOM); 491 LOG.debug("found custom auth principal " + customAuthPrincipalClass.getName() + " in subject. " + 492 "marking authentication method as " + AuthenticationMethod.CUSTOM); 493 } 494 } 495 496 /** 497 * checks if logged in using kerberos 498 * @return true if the subject logged via keytab or has a Kerberos TGT 499 */ 500 public boolean hasKerberosCredentials() { 501 return isKeytab || isKrbTkt; 502 } 503 504 public List<RpcAuthMethod> getRpcAuthMethodList() { 505 return rpcAuthMethodList; 506 } 507 508 /** 509 * Return the current user, including any doAs in the current stack. 510 * @return the current user 511 * @throws IOException if login fails 512 */ 513 @InterfaceAudience.Public 514 @InterfaceStability.Evolving 515 public synchronized 516 static UserGroupInformation getCurrentUser() throws IOException { 517 AccessControlContext context = AccessController.getContext(); 518 Subject subject = Subject.getSubject(context); 519 if (subject == null || subject.getPrincipals(User.class).isEmpty()) { 520 return getLoginUser(); 521 } else { 522 return new UserGroupInformation(subject); 523 } 524 } 525 526 /** 527 * Find the most appropriate UserGroupInformation to use 528 * 529 * @param ticketCachePath The Kerberos ticket cache path, or NULL 530 * if none is specfied 531 * @param user The user name, or NULL if none is specified. 532 * 533 * @return The most appropriate UserGroupInformation 534 */ 535 public static UserGroupInformation getBestUGI( 536 String ticketCachePath, String user) throws IOException { 537 if (ticketCachePath != null) { 538 return getUGIFromTicketCache(ticketCachePath, user); 539 } else if (user == null) { 540 return getCurrentUser(); 541 } else { 542 return createRemoteUser(user); 543 } 544 } 545 546 /** 547 * Create a UserGroupInformation from a Kerberos ticket cache. 548 * 549 * @param user The principal name to load from the ticket 550 * cache 551 * @param ticketCachePath the path to the ticket cache file 552 * 553 * @throws IOException if the kerberos login fails 554 */ 555 @InterfaceAudience.Public 556 @InterfaceStability.Evolving 557 public static UserGroupInformation getUGIFromTicketCache( 558 String ticketCache, String user) throws IOException { 559 if (!isAuthenticationMethodEnabled(AuthenticationMethod.KERBEROS)) { 560 return getBestUGI(null, user); 561 } 562 try { 563 Map<String,String> krbOptions = new HashMap<String,String>(); 564 if (IBM_JAVA) { 565 krbOptions.put("useDefaultCcache", "true"); 566 // The first value searched when "useDefaultCcache" is used. 567 System.setProperty("KRB5CCNAME", ticketCache); 568 } else { 569 krbOptions.put("ticketCache", ticketCache); 570 } 571 krbOptions.put("renewTGT", "false"); 572 LoginContext login = newLoginContext(userJAASConfName, null, krbOptions); 573 login.login(); 574 575 Subject loginSubject = login.getSubject(); 576 Set<Principal> loginPrincipals = loginSubject.getPrincipals(); 577 if (loginPrincipals.isEmpty()) { 578 throw new RuntimeException("No login principals found!"); 579 } 580 if (loginPrincipals.size() != 1) { 581 LOG.warn("found more than one principal in the ticket cache file " + 582 ticketCache); 583 } 584 User ugiUser = new User(loginPrincipals.iterator().next().getName(), 585 AuthenticationMethod.KERBEROS, login); 586 loginSubject.getPrincipals().add(ugiUser); 587 UserGroupInformation ugi = new UserGroupInformation(loginSubject); 588 ugi.setLogin(login); 589 ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 590 return ugi; 591 } catch (LoginException le) { 592 throw new IOException("failure to login using ticket cache file " + 593 ticketCache, le); 594 } 595 } 596 597 /** 598 * Create a UserGroupInformation from a Subject with Kerberos principal. 599 * 600 * @param user The KerberosPrincipal to use in UGI 601 * 602 * @throws IOException if the kerberos login fails 603 */ 604 public static UserGroupInformation getUGIFromSubject(Subject subject) 605 throws IOException { 606 if (subject == null) { 607 throw new IOException("Subject must not be null"); 608 } 609 610 if (subject.getPrincipals(KerberosPrincipal.class).isEmpty()) { 611 throw new IOException("Provided Subject must contain a KerberosPrincipal"); 612 } 613 614 KerberosPrincipal principal = 615 subject.getPrincipals(KerberosPrincipal.class).iterator().next(); 616 617 User ugiUser = new User(principal.getName(), 618 AuthenticationMethod.KERBEROS, null); 619 subject.getPrincipals().add(ugiUser); 620 UserGroupInformation ugi = new UserGroupInformation(subject); 621 ugi.setLogin(null); 622 ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 623 return ugi; 624 } 625 626 /** 627 * Get the currently logged in user. 628 * @return the logged in user 629 * @throws IOException if login fails 630 */ 631 @InterfaceAudience.Public 632 @InterfaceStability.Evolving 633 public synchronized 634 static UserGroupInformation getLoginUser() throws IOException { 635 if (loginUser == null) { 636 loginUserFromSubject(null); 637 } 638 return loginUser; 639 } 640 641 /** 642 * Log in a user using the given subject 643 * @parma subject the subject to use when logging in a user, or null to 644 * create a new subject. 645 * @throws IOException if login fails 646 */ 647 @InterfaceAudience.Public 648 @InterfaceStability.Evolving 649 public synchronized 650 static void loginUserFromSubject(Subject subject) throws IOException { 651 ensureInitialized(); 652 try { 653 if (subject == null) { 654 subject = new Subject(); 655 } 656 LoginContext login = newLoginContext(userJAASConfName, subject, null); 657 login.login(); 658 UserGroupInformation realUser = new UserGroupInformation(subject, userJAASConfName); 659 realUser.setLogin(login); 660 // AuthenticationMethod is now computed based upon subject 661 // loginUser.setAuthenticationMethod(...); 662 setUserAuthenticationMethod(realUser); 663 realUser = new UserGroupInformation(login.getSubject(), userJAASConfName); 664 // If the HADOOP_PROXY_USER environment variable or property 665 // is specified, create a proxy user as the logged in user. 666 String proxyUser = System.getenv(HADOOP_PROXY_USER); 667 if (proxyUser == null) { 668 proxyUser = System.getProperty(HADOOP_PROXY_USER); 669 } 670 loginUser = proxyUser == null ? realUser : createProxyUser(proxyUser, realUser); 671 672 String fileLocation = System.getenv(HADOOP_TOKEN_FILE_LOCATION); 673 if (fileLocation != null) { 674 // Load the token storage file and put all of the tokens into the 675 // user. Don't use the FileSystem API for reading since it has a lock 676 // cycle (HADOOP-9212). 677 Credentials cred = Credentials.readTokenStorageFile( 678 new File(fileLocation), conf); 679 loginUser.addCredentials(cred); 680 } 681 loginUser.spawnAutoRenewalThreadForUserCreds(); 682 } catch (LoginException le) { 683 LOG.debug("failure to login", le); 684 throw new IOException("failure to login: " + le.getMessage(), le); 685 } 686 if (LOG.isDebugEnabled()) { 687 LOG.debug("UGI loginUser:"+loginUser); 688 } 689 } 690 691 private static void setUserAuthenticationMethod(UserGroupInformation realUser) { 692 if (realUser.user.getAuthenticationMethod() == null) { // if the user's auth method is not set from subject 693 switch (authenticationMethod) { // set it from configured authenticationMethod 694 case TOKEN: 695 case CERTIFICATE: 696 case KERBEROS_SSL: 697 case PROXY: 698 throw new UnsupportedOperationException( 699 authenticationMethod + " login authentication is not supported"); 700 default: 701 LOG.debug("Found no authentication principals in subject. Simple?"); 702 realUser.user.setAuthenticationMethod(authenticationMethod); 703 } 704 } 705 } 706 707 @InterfaceAudience.Private 708 @InterfaceStability.Unstable 709 @VisibleForTesting 710 public synchronized static void setLoginUser(UserGroupInformation ugi) { 711 // if this is to become stable, should probably logout the currently 712 // logged in ugi if it's different 713 loginUser = ugi; 714 } 715 716 /** 717 * Is this user logged in from a keytab file? 718 * @return true if the credentials are from a keytab file. 719 */ 720 public boolean isFromKeytab() { 721 return isKeytab; 722 } 723 724 /** 725 * Get the Kerberos TGT 726 * @return the user's TGT or null if none was found 727 */ 728 private synchronized KerberosTicket getTGT() { 729 Set<KerberosTicket> tickets = subject 730 .getPrivateCredentials(KerberosTicket.class); 731 for (KerberosTicket ticket : tickets) { 732 if (SecurityUtil.isOriginalTGT(ticket)) { 733 if (LOG.isDebugEnabled()) { 734 LOG.debug("Found tgt " + ticket); 735 } 736 return ticket; 737 } 738 } 739 return null; 740 } 741 742 private long getRefreshTime(KerberosTicket tgt) { 743 long start = tgt.getStartTime().getTime(); 744 long end = tgt.getEndTime().getTime(); 745 return start + (long) ((end - start) * TICKET_RENEW_WINDOW); 746 } 747 748 /**Spawn a thread to do periodic renewals of kerberos credentials*/ 749 private void spawnAutoRenewalThreadForUserCreds() { 750 if (isSecurityEnabled()) { 751 //spawn thread only if we have kerb credentials 752 if (user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS && 753 !isKeytab) { 754 Thread t = new Thread(new Runnable() { 755 756 @Override 757 public void run() { 758 String cmd = conf.get("hadoop.kerberos.kinit.command", 759 "kinit"); 760 KerberosTicket tgt = getTGT(); 761 if (tgt == null) { 762 return; 763 } 764 long nextRefresh = getRefreshTime(tgt); 765 while (true) { 766 try { 767 long now = Time.now(); 768 if(LOG.isDebugEnabled()) { 769 LOG.debug("Current time is " + now); 770 LOG.debug("Next refresh is " + nextRefresh); 771 } 772 if (now < nextRefresh) { 773 Thread.sleep(nextRefresh - now); 774 } 775 Shell.execCommand(cmd, "-R"); 776 if(LOG.isDebugEnabled()) { 777 LOG.debug("renewed ticket"); 778 } 779 reloginFromTicketCache(); 780 tgt = getTGT(); 781 if (tgt == null) { 782 LOG.warn("No TGT after renewal. Aborting renew thread for " + 783 getUserName()); 784 return; 785 } 786 nextRefresh = Math.max(getRefreshTime(tgt), 787 now + MIN_TIME_BEFORE_RELOGIN); 788 } catch (InterruptedException ie) { 789 LOG.warn("Terminating renewal thread"); 790 return; 791 } catch (IOException ie) { 792 LOG.warn("Exception encountered while running the" + 793 " renewal command. Aborting renew thread. " + ie); 794 return; 795 } 796 } 797 } 798 }); 799 t.setDaemon(true); 800 t.setName("TGT Renewer for " + getUserName()); 801 t.start(); 802 } 803 } 804 } 805 /** 806 * Log a user in from a keytab file. Loads a user identity from a keytab 807 * file and logs them in. They become the currently logged-in user. 808 * @param user the principal name to load from the keytab 809 * @param path the path to the keytab file 810 * @throws IOException if the keytab file can't be read 811 */ 812 @InterfaceAudience.Public 813 @InterfaceStability.Evolving 814 public synchronized 815 static void loginUserFromKeytab(String user, 816 String path 817 ) throws IOException { 818 if (!isSecurityEnabled()) 819 return; 820 821 keytabFile = path; 822 keytabPrincipal = user; 823 Subject subject = new Subject(); 824 LoginContext login; 825 long start = 0; 826 try { 827 login = newLoginContextWithKeyTab(serviceJAASConfName, 828 subject, keytabFile, keytabPrincipal); 829 start = Time.now(); 830 login.login(); 831 metrics.loginSuccess.add(Time.now() - start); 832 loginUser = new UserGroupInformation(subject, serviceJAASConfName); 833 loginUser.setLogin(login); 834 // AuthenticationMethod is now computed based upon subject 835 // loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 836 } catch (LoginException le) { 837 if (start > 0) { 838 metrics.loginFailure.add(Time.now() - start); 839 } 840 throw new IOException("Login failure for " + user + " from keytab " + 841 path, le); 842 } 843 LOG.info("Login successful for user " + keytabPrincipal 844 + " using keytab file " + keytabFile); 845 } 846 847 /** 848 * Re-login a user from keytab if TGT is expired or is close to expiry. 849 * 850 * @throws IOException 851 */ 852 public synchronized void checkTGTAndReloginFromKeytab() throws IOException { 853 if (!isSecurityEnabled() 854 || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS 855 || !isKeytab) 856 return; 857 KerberosTicket tgt = getTGT(); 858 if (tgt != null && Time.now() < getRefreshTime(tgt)) { 859 return; 860 } 861 reloginFromKeytab(); 862 } 863 864 /** 865 * Re-Login a user in from a keytab file. Loads a user identity from a keytab 866 * file and logs them in. They become the currently logged-in user. This 867 * method assumes that {@link #loginUserFromKeytab(String, String)} had 868 * happened already. 869 * The Subject field of this UserGroupInformation object is updated to have 870 * the new credentials. 871 * @throws IOException on a failure 872 */ 873 @InterfaceAudience.Public 874 @InterfaceStability.Evolving 875 public synchronized void reloginFromKeytab() 876 throws IOException { 877 if (!isSecurityEnabled() || 878 user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS || 879 !isKeytab) 880 return; 881 882 long now = Time.now(); 883 if (!hasSufficientTimeElapsed(now)) { 884 return; 885 } 886 887 KerberosTicket tgt = getTGT(); 888 //Return if TGT is valid and is not going to expire soon. 889 if (tgt != null && now < getRefreshTime(tgt)) { 890 return; 891 } 892 893 LoginContext login = getLogin(); 894 if (login == null || keytabFile == null) { 895 throw new IOException("loginUserFromKeyTab must be done first"); 896 } 897 898 long start = 0; 899 // register most recent relogin attempt 900 user.setLastLogin(now); 901 try { 902 if (LOG.isDebugEnabled()) { 903 LOG.debug("Initiating logout for " + getUserName()); 904 } 905 synchronized (UserGroupInformation.class) { 906 // clear up the kerberos state. But the tokens are not cleared! As per 907 // the Java kerberos login module code, only the kerberos credentials 908 // are cleared 909 login.logout(); 910 // login and also update the subject field of this instance to 911 // have the new credentials (pass it to the LoginContext constructor) 912 login = newLoginContextWithKeyTab(serviceJAASConfName, getSubject(), 913 keytabFile, keytabPrincipal); 914 LOG.info("Initiating re-login for " + keytabPrincipal); 915 start = Time.now(); 916 login.login(); 917 metrics.loginSuccess.add(Time.now() - start); 918 setLogin(login); 919 } 920 } catch (LoginException le) { 921 if (start > 0) { 922 metrics.loginFailure.add(Time.now() - start); 923 } 924 throw new IOException("Login failure for " + keytabPrincipal + 925 " from keytab " + keytabFile, le); 926 } 927 } 928 929 /** 930 * Re-Login a user in from the ticket cache. This 931 * method assumes that login had happened already. 932 * The Subject field of this UserGroupInformation object is updated to have 933 * the new credentials. 934 * @throws IOException on a failure 935 */ 936 @InterfaceAudience.Public 937 @InterfaceStability.Evolving 938 public synchronized void reloginFromTicketCache() 939 throws IOException { 940 if (!isSecurityEnabled() || 941 user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS || 942 !isKrbTkt) 943 return; 944 LoginContext login = getLogin(); 945 if (login == null) { 946 throw new IOException("login must be done first"); 947 } 948 long now = Time.now(); 949 if (!hasSufficientTimeElapsed(now)) { 950 return; 951 } 952 // register most recent relogin attempt 953 user.setLastLogin(now); 954 try { 955 if (LOG.isDebugEnabled()) { 956 LOG.debug("Initiating logout for " + getUserName()); 957 } 958 //clear up the kerberos state. But the tokens are not cleared! As per 959 //the Java kerberos login module code, only the kerberos credentials 960 //are cleared 961 login.logout(); 962 //login and also update the subject field of this instance to 963 //have the new credentials (pass it to the LoginContext constructor) 964 login = newLoginContext(userJAASConfName, getSubject(), null); 965 LOG.info("Initiating re-login for " + getUserName()); 966 login.login(); 967 setLogin(login); 968 } catch (LoginException le) { 969 throw new IOException("Login failure for " + getUserName(), le); 970 } 971 } 972 973 974 /** 975 * Log a user in from a keytab file. Loads a user identity from a keytab 976 * file and login them in. This new user does not affect the currently 977 * logged-in user. 978 * @param user the principal name to load from the keytab 979 * @param path the path to the keytab file 980 * @throws IOException if the keytab file can't be read 981 */ 982 public synchronized 983 static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user, 984 String path 985 ) throws IOException { 986 if (!isSecurityEnabled()) 987 return UserGroupInformation.getCurrentUser(); 988 String oldKeytabFile = null; 989 String oldKeytabPrincipal = null; 990 991 long start = 0; 992 try { 993 oldKeytabFile = keytabFile; 994 oldKeytabPrincipal = keytabPrincipal; 995 keytabFile = path; 996 keytabPrincipal = user; 997 Subject subject = new Subject(); 998 999 LoginContext login = newLoginContextWithKeyTab(serviceJAASConfName, subject, 1000 keytabFile, keytabPrincipal); 1001 1002 start = Time.now(); 1003 login.login(); 1004 metrics.loginSuccess.add(Time.now() - start); 1005 UserGroupInformation newLoginUser = 1006 new UserGroupInformation(subject, serviceJAASConfName); 1007 newLoginUser.setLogin(login); 1008 // AuthenticationMethod is now computed based upon subject 1009 // newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 1010 return newLoginUser; 1011 } catch (LoginException le) { 1012 if (start > 0) { 1013 metrics.loginFailure.add(Time.now() - start); 1014 } 1015 throw new IOException("Login failure for " + user + " from keytab " + 1016 path, le); 1017 } finally { 1018 if(oldKeytabFile != null) keytabFile = oldKeytabFile; 1019 if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal; 1020 } 1021 } 1022 1023 private boolean hasSufficientTimeElapsed(long now) { 1024 if (now - user.getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) { 1025 LOG.warn("Not attempting to re-login since the last re-login was " + 1026 "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+ 1027 " before."); 1028 return false; 1029 } 1030 return true; 1031 } 1032 1033 /** 1034 * Did the login happen via keytab 1035 * @return true or false 1036 */ 1037 @InterfaceAudience.Public 1038 @InterfaceStability.Evolving 1039 public synchronized static boolean isLoginKeytabBased() throws IOException { 1040 return getLoginUser().isKeytab; 1041 } 1042 1043 /** 1044 * Did the login happen via ticket cache 1045 * @return true or false 1046 */ 1047 public static boolean isLoginTicketBased() throws IOException { 1048 return getLoginUser().isKrbTkt; 1049 } 1050 1051 /** 1052 * Create a user from a login name. It is intended to be used for remote 1053 * users in RPC, since it won't have any credentials. 1054 * @param user the full user principal name, must not be empty or null 1055 * @return the UserGroupInformation for the remote user. 1056 */ 1057 @InterfaceAudience.Public 1058 @InterfaceStability.Evolving 1059 public static UserGroupInformation createRemoteUser(String user) { 1060 return createRemoteUser(user, AuthMethod.SIMPLE); 1061 } 1062 1063 /** 1064 * Create a user from a login name. It is intended to be used for remote 1065 * users in RPC, since it won't have any credentials. 1066 * @param user the full user principal name, must not be empty or null 1067 * @return the UserGroupInformation for the remote user. 1068 */ 1069 @InterfaceAudience.Public 1070 @InterfaceStability.Evolving 1071 public static UserGroupInformation createRemoteUser(String user, AuthMethod authMethod) { 1072 if (user == null || user.isEmpty()) { 1073 throw new IllegalArgumentException("Null user"); 1074 } 1075 Subject subject = new Subject(); 1076 subject.getPrincipals().add(new User(user)); 1077 UserGroupInformation result = new UserGroupInformation(subject); 1078 result.setAuthenticationMethod(authMethod); 1079 return result; 1080 } 1081 1082 /** 1083 * existing types of authentications' methods 1084 */ 1085 @InterfaceAudience.Public 1086 @InterfaceStability.Evolving 1087 public static enum AuthenticationMethod { 1088 // currently we support only one auth per method, but eventually a 1089 // subtype is needed to differentiate, ex. if digest is token or ldap 1090 SIMPLE(false), 1091 KERBEROS(true), 1092 TOKEN(false), 1093 CERTIFICATE(true), 1094 KERBEROS_SSL(true), 1095 PROXY(false), 1096 CUSTOM(true); 1097 1098 private final boolean allowsDelegation; 1099 1100 private AuthenticationMethod(boolean allowsDelegation) { 1101 this.allowsDelegation = allowsDelegation; 1102 } 1103 1104 public boolean allowsDelegation() { 1105 return allowsDelegation; 1106 } 1107 } 1108 1109 /** 1110 * Create a proxy user using username of the effective user and the ugi of the 1111 * real user. 1112 * @param user 1113 * @param realUser 1114 * @return proxyUser ugi 1115 */ 1116 @InterfaceAudience.Public 1117 @InterfaceStability.Evolving 1118 public static UserGroupInformation createProxyUser(String user, 1119 UserGroupInformation realUser) { 1120 if (user == null || user.isEmpty()) { 1121 throw new IllegalArgumentException("Null user"); 1122 } 1123 if (realUser == null) { 1124 throw new IllegalArgumentException("Null real user"); 1125 } 1126 Subject subject = new Subject(); 1127 Set<Principal> principals = subject.getPrincipals(); 1128 principals.add(new User(user)); 1129 principals.add(new RealUser(realUser)); 1130 UserGroupInformation result =new UserGroupInformation(subject); 1131 result.setAuthenticationMethod(AuthenticationMethod.PROXY); 1132 return result; 1133 } 1134 1135 /** 1136 * get RealUser (vs. EffectiveUser) 1137 * @return realUser running over proxy user 1138 */ 1139 @InterfaceAudience.Public 1140 @InterfaceStability.Evolving 1141 public UserGroupInformation getRealUser() { 1142 for (RealUser p: subject.getPrincipals(RealUser.class)) { 1143 return p.getRealUser(); 1144 } 1145 return null; 1146 } 1147 1148 1149 1150 /** 1151 * This class is used for storing the groups for testing. It stores a local 1152 * map that has the translation of usernames to groups. 1153 */ 1154 private static class TestingGroups extends Groups { 1155 private final Map<String, List<String>> userToGroupsMapping = 1156 new HashMap<String,List<String>>(); 1157 private Groups underlyingImplementation; 1158 1159 private TestingGroups(Groups underlyingImplementation) { 1160 super(new org.apache.hadoop.conf.Configuration()); 1161 this.underlyingImplementation = underlyingImplementation; 1162 } 1163 1164 @Override 1165 public List<String> getGroups(String user) throws IOException { 1166 List<String> result = userToGroupsMapping.get(user); 1167 1168 if (result == null) { 1169 result = underlyingImplementation.getGroups(user); 1170 } 1171 1172 return result; 1173 } 1174 1175 private void setUserGroups(String user, String[] groups) { 1176 userToGroupsMapping.put(user, Arrays.asList(groups)); 1177 } 1178 } 1179 1180 /** 1181 * Create a UGI for testing HDFS and MapReduce 1182 * @param user the full user principal name 1183 * @param userGroups the names of the groups that the user belongs to 1184 * @return a fake user for running unit tests 1185 */ 1186 @InterfaceAudience.Public 1187 @InterfaceStability.Evolving 1188 public static UserGroupInformation createUserForTesting(String user, 1189 String[] userGroups) { 1190 ensureInitialized(); 1191 UserGroupInformation ugi = createRemoteUser(user); 1192 // make sure that the testing object is setup 1193 if (!(groups instanceof TestingGroups)) { 1194 groups = new TestingGroups(groups); 1195 } 1196 // add the user groups 1197 ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups); 1198 return ugi; 1199 } 1200 1201 1202 /** 1203 * Create a proxy user UGI for testing HDFS and MapReduce 1204 * 1205 * @param user 1206 * the full user principal name for effective user 1207 * @param realUser 1208 * UGI of the real user 1209 * @param userGroups 1210 * the names of the groups that the user belongs to 1211 * @return a fake user for running unit tests 1212 */ 1213 public static UserGroupInformation createProxyUserForTesting(String user, 1214 UserGroupInformation realUser, String[] userGroups) { 1215 ensureInitialized(); 1216 UserGroupInformation ugi = createProxyUser(user, realUser); 1217 // make sure that the testing object is setup 1218 if (!(groups instanceof TestingGroups)) { 1219 groups = new TestingGroups(groups); 1220 } 1221 // add the user groups 1222 ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups); 1223 return ugi; 1224 } 1225 1226 /** 1227 * Get the user's login name. 1228 * @return the user's name up to the first '/' or '@'. 1229 */ 1230 public String getShortUserName() { 1231 if (windows && spoofUser) { 1232 return spoofedUser; 1233 } 1234 1235 for (User p: subject.getPrincipals(User.class)) { 1236 return p.getShortName(); 1237 } 1238 return null; 1239 } 1240 1241 public String getPrimaryGroupName() throws IOException { 1242 String[] groups = getGroupNames(); 1243 if (groups.length == 0) { 1244 throw new IOException("There is no primary group for UGI " + this); 1245 } 1246 return groups[0]; 1247 } 1248 1249 /** 1250 * Get the user's full principal name. 1251 * @return the user's full principal name. 1252 */ 1253 @InterfaceAudience.Public 1254 @InterfaceStability.Evolving 1255 public String getUserName() { 1256 if (windows && spoofUser) { 1257 return spoofedUser; 1258 } 1259 1260 return user.getName(); 1261 } 1262 1263 /** 1264 * Add a TokenIdentifier to this UGI. The TokenIdentifier has typically been 1265 * authenticated by the RPC layer as belonging to the user represented by this 1266 * UGI. 1267 * 1268 * @param tokenId 1269 * tokenIdentifier to be added 1270 * @return true on successful add of new tokenIdentifier 1271 */ 1272 public synchronized boolean addTokenIdentifier(TokenIdentifier tokenId) { 1273 return subject.getPublicCredentials().add(tokenId); 1274 } 1275 1276 /** 1277 * Get the set of TokenIdentifiers belonging to this UGI 1278 * 1279 * @return the set of TokenIdentifiers belonging to this UGI 1280 */ 1281 public synchronized Set<TokenIdentifier> getTokenIdentifiers() { 1282 return subject.getPublicCredentials(TokenIdentifier.class); 1283 } 1284 1285 /** 1286 * Add a token to this UGI 1287 * 1288 * @param token Token to be added 1289 * @return true on successful add of new token 1290 */ 1291 public boolean addToken(Token<? extends TokenIdentifier> token) { 1292 return (token != null) ? addToken(token.getService(), token) : false; 1293 } 1294 1295 /** 1296 * Add a named token to this UGI 1297 * 1298 * @param alias Name of the token 1299 * @param token Token to be added 1300 * @return true on successful add of new token 1301 */ 1302 public boolean addToken(Text alias, Token<? extends TokenIdentifier> token) { 1303 synchronized (subject) { 1304 getCredentialsInternal().addToken(alias, token); 1305 return true; 1306 } 1307 } 1308 1309 /** 1310 * Obtain the collection of tokens associated with this user. 1311 * 1312 * @return an unmodifiable collection of tokens associated with user 1313 */ 1314 public Collection<Token<? extends TokenIdentifier>> getTokens() { 1315 synchronized (subject) { 1316 return Collections.unmodifiableCollection( 1317 new ArrayList<Token<?>>(getCredentialsInternal().getAllTokens())); 1318 } 1319 } 1320 1321 /** 1322 * Obtain the tokens in credentials form associated with this user. 1323 * 1324 * @return Credentials of tokens associated with this user 1325 */ 1326 public Credentials getCredentials() { 1327 synchronized (subject) { 1328 Credentials creds = new Credentials(getCredentialsInternal()); 1329 Iterator<Token<?>> iter = creds.getAllTokens().iterator(); 1330 while (iter.hasNext()) { 1331 if (iter.next() instanceof Token.PrivateToken) { 1332 iter.remove(); 1333 } 1334 } 1335 return creds; 1336 } 1337 } 1338 1339 /** 1340 * Add the given Credentials to this user. 1341 * @param credentials of tokens and secrets 1342 */ 1343 public void addCredentials(Credentials credentials) { 1344 synchronized (subject) { 1345 getCredentialsInternal().addAll(credentials); 1346 } 1347 } 1348 1349 private synchronized Credentials getCredentialsInternal() { 1350 final Credentials credentials; 1351 final Set<Credentials> credentialsSet = 1352 subject.getPrivateCredentials(Credentials.class); 1353 if (!credentialsSet.isEmpty()){ 1354 credentials = credentialsSet.iterator().next(); 1355 } else { 1356 credentials = new Credentials(); 1357 subject.getPrivateCredentials().add(credentials); 1358 } 1359 return credentials; 1360 } 1361 1362 /** 1363 * Get the group names for this user. 1364 * @return the list of users with the primary group first. If the command 1365 * fails, it returns an empty list. 1366 */ 1367 public synchronized String[] getGroupNames() { 1368 ensureInitialized(); 1369 try { 1370 Set<String> result = new LinkedHashSet<String> 1371 (groups.getGroups(getShortUserName())); 1372 return result.toArray(new String[result.size()]); 1373 } catch (IOException ie) { 1374 LOG.warn("No groups available for user " + getShortUserName()); 1375 return new String[0]; 1376 } 1377 } 1378 1379 /** 1380 * Return the username. 1381 */ 1382 @Override 1383 public String toString() { 1384 StringBuilder sb = new StringBuilder(getUserName()); 1385 sb.append(" (auth:"+getAuthenticationMethod()+")"); 1386 if (getRealUser() != null) { 1387 sb.append(" via ").append(getRealUser().toString()); 1388 } 1389 return sb.toString(); 1390 } 1391 1392 /** 1393 * Sets the authentication method in the subject 1394 * 1395 * @param authMethod 1396 */ 1397 public synchronized 1398 void setAuthenticationMethod(AuthenticationMethod authMethod) { 1399 user.setAuthenticationMethod(authMethod); 1400 } 1401 1402 /** 1403 * Sets the authentication method in the subject 1404 * 1405 * @param authMethod 1406 */ 1407 //TODO: Delete this method once AuthMethod is deprecated. 1408 public void setAuthenticationMethod(AuthMethod authMethod) { 1409 switch (authMethod) { 1410 case SIMPLE: 1411 user.setAuthenticationMethod(AuthenticationMethod.SIMPLE); 1412 break; 1413 case KERBEROS: { 1414 user.setAuthenticationMethod(AuthenticationMethod.KERBEROS); 1415 break; 1416 } 1417 case DIGEST: 1418 case TOKEN: { 1419 user.setAuthenticationMethod(AuthenticationMethod.TOKEN); 1420 break; 1421 } 1422 default: 1423 user.setAuthenticationMethod(null); 1424 } 1425 } 1426 1427 /** 1428 * Get the authentication method from the subject 1429 * 1430 * @return AuthenticationMethod in the subject, null if not present. 1431 */ 1432 public synchronized AuthenticationMethod getAuthenticationMethod() { 1433 return user.getAuthenticationMethod(); 1434 } 1435 1436 /** 1437 * Get the authentication method from the real user's subject. If there 1438 * is no real user, return the given user's authentication method. 1439 * 1440 * @return AuthenticationMethod in the subject, null if not present. 1441 */ 1442 public synchronized AuthenticationMethod getRealAuthenticationMethod() { 1443 UserGroupInformation ugi = getRealUser(); 1444 if (ugi == null) { 1445 ugi = this; 1446 } 1447 return ugi.getAuthenticationMethod(); 1448 } 1449 1450 /** 1451 * Returns the authentication method of a ugi. If the authentication method is 1452 * PROXY, returns the authentication method of the real user. 1453 * 1454 * @param ugi 1455 * @return AuthenticationMethod 1456 */ 1457 public static AuthenticationMethod getRealAuthenticationMethod( 1458 UserGroupInformation ugi) { 1459 AuthenticationMethod authMethod = ugi.getAuthenticationMethod(); 1460 if (authMethod == AuthenticationMethod.PROXY) { 1461 authMethod = ugi.getRealUser().getAuthenticationMethod(); 1462 } 1463 return authMethod; 1464 } 1465 1466 /** 1467 * Compare the subjects to see if they are equal to each other. 1468 */ 1469 @Override 1470 public boolean equals(Object o) { 1471 if (o == this) { 1472 return true; 1473 } else if (o == null || getClass() != o.getClass()) { 1474 return false; 1475 } else { 1476 return subject == ((UserGroupInformation) o).subject; 1477 } 1478 } 1479 1480 /** 1481 * Return the hash of the subject. 1482 */ 1483 @Override 1484 public int hashCode() { 1485 return System.identityHashCode(subject); 1486 } 1487 1488 /** 1489 * Get the underlying subject from this ugi. 1490 * @return the subject that represents this user. 1491 */ 1492 public Subject getSubject() { 1493 return subject; 1494 } 1495 1496 /** 1497 * Run the given action as the user. 1498 * @param <T> the return type of the run method 1499 * @param action the method to execute 1500 * @return the value from the run method 1501 */ 1502 @InterfaceAudience.Public 1503 @InterfaceStability.Evolving 1504 public <T> T doAs(PrivilegedAction<T> action) { 1505 logPrivilegedAction(subject, action); 1506 return Subject.doAs(subject, action); 1507 } 1508 1509 /** 1510 * Run the given action as the user, potentially throwing an exception. 1511 * @param <T> the return type of the run method 1512 * @param action the method to execute 1513 * @return the value from the run method 1514 * @throws IOException if the action throws an IOException 1515 * @throws Error if the action throws an Error 1516 * @throws RuntimeException if the action throws a RuntimeException 1517 * @throws InterruptedException if the action throws an InterruptedException 1518 * @throws UndeclaredThrowableException if the action throws something else 1519 */ 1520 @InterfaceAudience.Public 1521 @InterfaceStability.Evolving 1522 public <T> T doAs(PrivilegedExceptionAction<T> action 1523 ) throws IOException, InterruptedException { 1524 try { 1525 logPrivilegedAction(subject, action); 1526 return Subject.doAs(subject, action); 1527 } catch (PrivilegedActionException pae) { 1528 Throwable cause = pae.getCause(); 1529 if (LOG.isDebugEnabled()) { 1530 LOG.debug("PrivilegedActionException as:" + this + " cause:" + cause); 1531 } 1532 if (cause instanceof IOException) { 1533 throw (IOException) cause; 1534 } else if (cause instanceof Error) { 1535 throw (Error) cause; 1536 } else if (cause instanceof RuntimeException) { 1537 throw (RuntimeException) cause; 1538 } else if (cause instanceof InterruptedException) { 1539 throw (InterruptedException) cause; 1540 } else { 1541 throw new UndeclaredThrowableException(cause); 1542 } 1543 } 1544 } 1545 1546 private void logPrivilegedAction(Subject subject, Object action) { 1547 if (LOG.isDebugEnabled()) { 1548 // would be nice if action included a descriptive toString() 1549 String where = new Throwable().getStackTrace()[2].toString(); 1550 LOG.debug("PrivilegedAction as:"+this+" from:"+where); 1551 } 1552 } 1553 1554 private void print() throws IOException { 1555 System.out.println("User: " + getUserName()); 1556 System.out.print("Group Ids: "); 1557 System.out.println(); 1558 String[] groups = getGroupNames(); 1559 System.out.print("Groups: "); 1560 for(int i=0; i < groups.length; i++) { 1561 System.out.print(groups[i] + " "); 1562 } 1563 System.out.println(); 1564 } 1565 1566 /** 1567 * A test method to print out the current user's UGI. 1568 * @param args if there are two arguments, read the user from the keytab 1569 * and print it out. 1570 * @throws Exception 1571 */ 1572 public static void main(String [] args) throws Exception { 1573 System.out.println("Getting UGI for current user"); 1574 UserGroupInformation ugi = getCurrentUser(); 1575 ugi.print(); 1576 System.out.println("UGI: " + ugi); 1577 System.out.println("Auth method " + ugi.user.getAuthenticationMethod()); 1578 System.out.println("Keytab " + ugi.isKeytab); 1579 System.out.println("============================================================"); 1580 1581 if (args.length == 2) { 1582 System.out.println("Getting UGI from keytab...."); 1583 loginUserFromKeytab(args[0], args[1]); 1584 getCurrentUser().print(); 1585 System.out.println("Keytab: " + ugi); 1586 System.out.println("Auth method " + loginUser.user.getAuthenticationMethod()); 1587 System.out.println("Keytab " + loginUser.isKeytab); 1588 } 1589 } 1590 1591}