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