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