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