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