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