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