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