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