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