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