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