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