org.apache.hadoop.security.UserGroupInformation Maven / Gradle / Ivy
The newest version!
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.security;
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.kerberos.KerberosKey;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.spi.LoginModule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.metrics.MetricsContext;
import org.apache.hadoop.metrics.MetricsRecord;
import org.apache.hadoop.metrics.MetricsUtil;
import org.apache.hadoop.metrics.Updater;
import org.apache.hadoop.metrics.util.MetricsBase;
import org.apache.hadoop.metrics.util.MetricsRegistry;
import org.apache.hadoop.metrics.util.MetricsTimeVaryingRate;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.util.Shell;
import com.sun.security.auth.NTUserPrincipal;
import com.sun.security.auth.UnixPrincipal;
import com.sun.security.auth.module.Krb5LoginModule;
/**
* User and group information for Hadoop.
* This class wraps around a JAAS Subject and provides methods to determine the
* user's username and groups. It supports both the Windows, Unix and Kerberos
* login modules.
*/
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Evolving
public class UserGroupInformation {
private static final Log LOG = LogFactory.getLog(UserGroupInformation.class);
/**
* Percentage of the ticket window to use before we renew ticket.
*/
private static final float TICKET_RENEW_WINDOW = 0.80f;
/**
* UgiMetrics maintains UGI activity statistics
* and publishes them through the metrics interfaces.
*/
static class UgiMetrics implements Updater {
final MetricsTimeVaryingRate loginSuccess;
final MetricsTimeVaryingRate loginFailure;
private final MetricsRecord metricsRecord;
private final MetricsRegistry registry;
UgiMetrics() {
registry = new MetricsRegistry();
loginSuccess = new MetricsTimeVaryingRate("loginSuccess", registry,
"Rate of successful kerberos logins and time taken in milliseconds");
loginFailure = new MetricsTimeVaryingRate("loginFailure", registry,
"Rate of failed kerberos logins and time taken in milliseconds");
final MetricsContext metricsContext = MetricsUtil.getContext("ugi");
metricsRecord = MetricsUtil.createRecord(metricsContext, "ugi");
metricsContext.registerUpdater(this);
}
/**
* Push the metrics to the monitoring subsystem on doUpdate() call.
*/
@Override
public void doUpdates(final MetricsContext context) {
synchronized (this) {
for (MetricsBase m : registry.getMetricsList()) {
m.pushMetric(metricsRecord);
}
}
metricsRecord.update();
}
}
/**
* A login module that looks at the Kerberos, Unix, or Windows principal and
* adds the corresponding UserName.
*/
@InterfaceAudience.Private
public static class HadoopLoginModule implements LoginModule {
private Subject subject;
@Override
public boolean abort() throws LoginException {
return true;
}
private T getCanonicalUser(Class cls) {
for(T user: subject.getPrincipals(cls)) {
return user;
}
return null;
}
@Override
public boolean commit() throws LoginException {
// if we already have a user, we are done.
if (!subject.getPrincipals(User.class).isEmpty()) {
return true;
}
Principal user = null;
// if we are using kerberos, try it out
if (useKerberos) {
user = getCanonicalUser(KerberosPrincipal.class);
}
// if we don't have a kerberos user, use the OS user
if (user == null) {
user = getCanonicalUser(OS_PRINCIPAL_CLASS);
}
// if we found the user, add our principal
if (user != null) {
subject.getPrincipals().add(new User(user.getName()));
return true;
}
LOG.error("Can't find user in " + subject);
throw new LoginException("Can't find user name");
}
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
}
@Override
public boolean login() throws LoginException {
return true;
}
@Override
public boolean logout() throws LoginException {
return true;
}
}
/** Metrics to track UGI activity */
static UgiMetrics metrics = new UgiMetrics();
/** Are the static variables that depend on configuration initialized? */
private static boolean isInitialized = false;
/** Should we use Kerberos configuration? */
private static boolean useKerberos;
/** Server-side groups fetching service */
private static Groups groups;
/** The configuration to use */
private static Configuration conf;
/** Leave 10 minutes between relogin attempts. */
private static final long MIN_TIME_BEFORE_RELOGIN = 10 * 60 * 1000L;
/**Environment variable pointing to the token cache file*/
public static final String HADOOP_TOKEN_FILE_LOCATION =
"HADOOP_TOKEN_FILE_LOCATION";
/**
* A method to initialize the fields that depend on a configuration.
* Must be called before useKerberos or groups is used.
*/
private static synchronized void ensureInitialized() {
if (!isInitialized) {
initialize(new Configuration());
}
}
/**
* Initialize UGI and related classes.
* @param conf the configuration to use
*/
private static synchronized void initialize(Configuration conf) {
initUGI(conf);
// give the configuration on how to translate Kerberos names
try {
KerberosName.setConfiguration(conf);
} catch (IOException ioe) {
throw new RuntimeException("Problem with Kerberos auth_to_local name " +
"configuration", ioe);
}
}
/**
* Set the configuration values for UGI.
* @param conf the configuration to use
*/
private static synchronized void initUGI(Configuration conf) {
String value = conf.get(HADOOP_SECURITY_AUTHENTICATION);
if (value == null || "simple".equals(value)) {
useKerberos = false;
} else if ("kerberos".equals(value)) {
useKerberos = true;
} else {
throw new IllegalArgumentException("Invalid attribute value for " +
HADOOP_SECURITY_AUTHENTICATION +
" of " + value);
}
// If we haven't set up testing groups, use the configuration to find it
if (!(groups instanceof TestingGroups)) {
groups = Groups.getUserToGroupsMappingService(conf);
}
// Set the configuration for JAAS to be the Hadoop configuration.
// This is done here rather than a static initializer to avoid a
// circular dependence.
javax.security.auth.login.Configuration existingConfig = null;
try {
existingConfig =
javax.security.auth.login.Configuration.getConfiguration();
} catch (SecurityException se) {
// If no security configuration is on the classpath, then
// we catch this exception, and we don't need to delegate
// to anyone
}
if (existingConfig instanceof HadoopConfiguration) {
LOG.info("JAAS Configuration already set up for Hadoop, not re-installing.");
} else {
javax.security.auth.login.Configuration.setConfiguration(
new HadoopConfiguration(existingConfig));
}
isInitialized = true;
UserGroupInformation.conf = conf;
}
/**
* Set the static configuration for UGI.
* In particular, set the security authentication mechanism and the
* group look up service.
* @param conf the configuration to use
*/
public static void setConfiguration(Configuration conf) {
initialize(conf);
}
/**
* Determine if UserGroupInformation is using Kerberos to determine
* user identities or is relying on simple authentication
*
* @return true if UGI is working in a secure environment
*/
public static boolean isSecurityEnabled() {
ensureInitialized();
return useKerberos;
}
/**
* Information about the logged in user.
*/
private static UserGroupInformation loginUser = null;
private static String keytabPrincipal = null;
private static String keytabFile = null;
private final Subject subject;
// All non-static fields must be read-only caches that come from the subject.
private final User user;
private final boolean isKeytab;
private final boolean isKrbTkt;
private static final String OS_LOGIN_MODULE_NAME;
private static final Class extends Principal> OS_PRINCIPAL_CLASS;
private static final boolean windows =
System.getProperty("os.name").startsWith("Windows");
static {
if (windows) {
OS_LOGIN_MODULE_NAME = "com.sun.security.auth.module.NTLoginModule";
OS_PRINCIPAL_CLASS = NTUserPrincipal.class;
} else {
OS_LOGIN_MODULE_NAME = "com.sun.security.auth.module.UnixLoginModule";
OS_PRINCIPAL_CLASS = UnixPrincipal.class;
}
}
private static class RealUser implements Principal {
private final UserGroupInformation realUser;
RealUser(UserGroupInformation realUser) {
this.realUser = realUser;
}
public String getName() {
return realUser.getUserName();
}
public UserGroupInformation getRealUser() {
return realUser;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
} else {
return realUser.equals(((RealUser) o).realUser);
}
}
@Override
public int hashCode() {
return realUser.hashCode();
}
@Override
public String toString() {
return realUser.toString();
}
}
/**
* A JAAS configuration that defines the login modules that we want
* to use for login.
*/
private static class HadoopConfiguration
extends javax.security.auth.login.Configuration {
private static final String SIMPLE_CONFIG_NAME = "hadoop-simple";
private static final String USER_KERBEROS_CONFIG_NAME =
"hadoop-user-kerberos";
private static final String KEYTAB_KERBEROS_CONFIG_NAME =
"hadoop-keytab-kerberos";
private static final Map BASIC_JAAS_OPTIONS =
new HashMap();
static {
String jaasEnvVar = System.getenv("HADOOP_JAAS_DEBUG");
if (jaasEnvVar != null && "true".equalsIgnoreCase(jaasEnvVar)) {
BASIC_JAAS_OPTIONS.put("debug", "true");
}
}
private static final AppConfigurationEntry OS_SPECIFIC_LOGIN =
new AppConfigurationEntry(OS_LOGIN_MODULE_NAME,
LoginModuleControlFlag.REQUIRED,
BASIC_JAAS_OPTIONS);
private static final AppConfigurationEntry HADOOP_LOGIN =
new AppConfigurationEntry(HadoopLoginModule.class.getName(),
LoginModuleControlFlag.REQUIRED,
BASIC_JAAS_OPTIONS);
private static final Map USER_KERBEROS_OPTIONS =
new HashMap();
static {
USER_KERBEROS_OPTIONS.put("doNotPrompt", "true");
USER_KERBEROS_OPTIONS.put("useTicketCache", "true");
USER_KERBEROS_OPTIONS.put("renewTGT", "true");
String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) {
USER_KERBEROS_OPTIONS.put("ticketCache", ticketCache);
}
USER_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS);
}
private static final AppConfigurationEntry USER_KERBEROS_LOGIN =
new AppConfigurationEntry(Krb5LoginModule.class.getName(),
LoginModuleControlFlag.OPTIONAL,
USER_KERBEROS_OPTIONS);
private static final Map KEYTAB_KERBEROS_OPTIONS =
new HashMap();
static {
KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
KEYTAB_KERBEROS_OPTIONS.put("refreshKrb5Config", "true");
KEYTAB_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS);
}
private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN =
new AppConfigurationEntry(Krb5LoginModule.class.getName(),
LoginModuleControlFlag.REQUIRED,
KEYTAB_KERBEROS_OPTIONS);
private static final AppConfigurationEntry[] SIMPLE_CONF =
new AppConfigurationEntry[]{OS_SPECIFIC_LOGIN, HADOOP_LOGIN};
private static final AppConfigurationEntry[] USER_KERBEROS_CONF =
new AppConfigurationEntry[]{OS_SPECIFIC_LOGIN, USER_KERBEROS_LOGIN,
HADOOP_LOGIN};
private static final AppConfigurationEntry[] KEYTAB_KERBEROS_CONF =
new AppConfigurationEntry[]{KEYTAB_KERBEROS_LOGIN, HADOOP_LOGIN};
private final javax.security.auth.login.Configuration parent;
HadoopConfiguration(javax.security.auth.login.Configuration parent) {
this.parent = parent;
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
if (SIMPLE_CONFIG_NAME.equals(appName)) {
return SIMPLE_CONF;
} else if (USER_KERBEROS_CONFIG_NAME.equals(appName)) {
return USER_KERBEROS_CONF;
} else if (KEYTAB_KERBEROS_CONFIG_NAME.equals(appName)) {
KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile);
KEYTAB_KERBEROS_OPTIONS.put("principal", keytabPrincipal);
return KEYTAB_KERBEROS_CONF;
} else if (parent != null) {
return parent.getAppConfigurationEntry(appName);
}
return null;
}
}
private LoginContext getLogin() {
return user.getLogin();
}
private void setLogin(LoginContext login) {
user.setLogin(login);
}
/**
* Create a UserGroupInformation for the given subject.
* This does not change the subject or acquire new credentials.
* @param subject the user's subject
*/
UserGroupInformation(Subject subject) {
this.subject = subject;
this.user = subject.getPrincipals(User.class).iterator().next();
this.isKeytab = !subject.getPrivateCredentials(KerberosKey.class).isEmpty();
this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
}
/**
* checks if logged in using kerberos
* @return true if the subject logged via keytab or has a Kerberos TGT
*/
public boolean hasKerberosCredentials() {
return isKeytab || isKrbTkt;
}
/**
* Return the current user, including any doAs in the current stack.
* @return the current user
* @throws IOException if login fails
*/
public static UserGroupInformation getCurrentUser() throws IOException {
AccessControlContext context = AccessController.getContext();
Subject subject = Subject.getSubject(context);
if (subject == null || subject.getPrincipals(User.class).isEmpty()) {
return getLoginUser();
} else {
return new UserGroupInformation(subject);
}
}
/**
* Get the currently logged in user.
* @return the logged in user
* @throws IOException if login fails
*/
public synchronized
static UserGroupInformation getLoginUser() throws IOException {
if (loginUser == null) {
try {
Subject subject = new Subject();
LoginContext login;
if (isSecurityEnabled()) {
login = new LoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME,
subject);
} else {
login = new LoginContext(HadoopConfiguration.SIMPLE_CONFIG_NAME,
subject);
}
login.login();
loginUser = new UserGroupInformation(subject);
loginUser.setLogin(login);
loginUser.setAuthenticationMethod(isSecurityEnabled() ?
AuthenticationMethod.KERBEROS :
AuthenticationMethod.SIMPLE);
loginUser = new UserGroupInformation(login.getSubject());
String fileLocation = System.getenv(HADOOP_TOKEN_FILE_LOCATION);
if (fileLocation != null && isSecurityEnabled()) {
// load the token storage file and put all of the tokens into the
// user.
Credentials cred = Credentials.readTokenStorageFile(
new Path("file:///" + fileLocation), conf);
for (Token> token: cred.getAllTokens()) {
loginUser.addToken(token);
}
}
loginUser.spawnAutoRenewalThreadForUserCreds();
} catch (LoginException le) {
throw new IOException("failure to login", le);
}
}
return loginUser;
}
/**
* Is this user logged in from a keytab file?
* @return true if the credentials are from a keytab file.
*/
public boolean isFromKeytab() {
return isKeytab;
}
/**
* Get the Kerberos TGT
* @return the user's TGT or null if none was found
*/
private synchronized KerberosTicket getTGT() {
Set tickets = subject
.getPrivateCredentials(KerberosTicket.class);
for (KerberosTicket ticket : tickets) {
if (SecurityUtil.isOriginalTGT(ticket)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Found tgt " + ticket);
}
return ticket;
}
}
return null;
}
private long getRefreshTime(KerberosTicket tgt) {
long start = tgt.getStartTime().getTime();
long end = tgt.getEndTime().getTime();
return start + (long) ((end - start) * TICKET_RENEW_WINDOW);
}
/**Spawn a thread to do periodic renewals of kerberos credentials*/
private void spawnAutoRenewalThreadForUserCreds() {
if (isSecurityEnabled()) {
//spawn thread only if we have kerb credentials
if (user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS &&
!isKeytab) {
Thread t = new Thread(new Runnable() {
public void run() {
String cmd = conf.get("hadoop.kerberos.kinit.command",
"kinit");
KerberosTicket tgt = getTGT();
if (tgt == null) {
return;
}
long nextRefresh = getRefreshTime(tgt);
while (true) {
try {
long now = System.currentTimeMillis();
if(LOG.isDebugEnabled()) {
LOG.debug("Current time is " + now);
LOG.debug("Next refresh is " + nextRefresh);
}
if (now < nextRefresh) {
Thread.sleep(nextRefresh - now);
}
Shell.execCommand(cmd, "-R");
if(LOG.isDebugEnabled()) {
LOG.debug("renewed ticket");
}
reloginFromTicketCache();
tgt = getTGT();
if (tgt == null) {
LOG.warn("No TGT after renewal. Aborting renew thread for " +
getUserName());
return;
}
nextRefresh = Math.max(getRefreshTime(tgt),
now + MIN_TIME_BEFORE_RELOGIN);
} catch (InterruptedException ie) {
LOG.warn("Terminating renewal thread");
return;
} catch (IOException ie) {
LOG.warn("Exception encountered while running the" +
" renewal command. Aborting renew thread. " + ie);
return;
}
}
}
});
t.setDaemon(true);
t.setName("TGT Renewer for " + getUserName());
t.start();
}
}
}
/**
* Log a user in from a keytab file. Loads a user identity from a keytab
* file and logs them in. They become the currently logged-in user.
* @param user the principal name to load from the keytab
* @param path the path to the keytab file
* @throws IOException if the keytab file can't be read
*/
public synchronized
static void loginUserFromKeytab(String user,
String path
) throws IOException {
if (!isSecurityEnabled())
return;
keytabFile = path;
keytabPrincipal = user;
Subject subject = new Subject();
LoginContext login;
long start = 0;
try {
login =
new LoginContext(HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, subject);
start = System.currentTimeMillis();
login.login();
metrics.loginSuccess.inc(System.currentTimeMillis() - start);
loginUser = new UserGroupInformation(subject);
loginUser.setLogin(login);
loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
} catch (LoginException le) {
if (start > 0) {
metrics.loginFailure.inc(System.currentTimeMillis() - start);
}
throw new IOException("Login failure for " + user + " from keytab " +
path, le);
}
LOG.info("Login successful for user " + keytabPrincipal
+ " using keytab file " + keytabFile);
}
/**
* Re-Login a user in from a keytab file. Loads a user identity from a keytab
* file and logs them in. They become the currently logged-in user. This
* method assumes that {@link #loginUserFromKeytab(String, String)} had
* happened already.
* The Subject field of this UserGroupInformation object is updated to have
* the new credentials.
* @throws IOException on a failure
*/
public synchronized void reloginFromKeytab()
throws IOException {
if (!isSecurityEnabled() ||
user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
!isKeytab)
return;
long now = System.currentTimeMillis();
if (!hasSufficientTimeElapsed(now)) {
return;
}
KerberosTicket tgt = getTGT();
//Return if TGT is valid and is not going to expire soon.
if (tgt != null && now < getRefreshTime(tgt)) {
return;
}
LoginContext login = getLogin();
if (login == null || keytabFile == null) {
throw new IOException("loginUserFromKeyTab must be done first");
}
long start = 0;
// register most recent relogin attempt
user.setLastLogin(now);
try {
LOG.info("Initiating logout for " + getUserName());
synchronized (UserGroupInformation.class) {
// clear up the kerberos state. But the tokens are not cleared! As per
// the Java kerberos login module code, only the kerberos credentials
// are cleared
login.logout();
// login and also update the subject field of this instance to
// have the new credentials (pass it to the LoginContext constructor)
login = new LoginContext(
HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, getSubject());
LOG.info("Initiating re-login for " + keytabPrincipal);
start = System.currentTimeMillis();
login.login();
metrics.loginSuccess.inc(System.currentTimeMillis() - start);
setLogin(login);
}
} catch (LoginException le) {
if (start > 0) {
metrics.loginFailure.inc(System.currentTimeMillis() - start);
}
throw new IOException("Login failure for " + keytabPrincipal +
" from keytab " + keytabFile, le);
}
}
/**
* Re-Login a user in from the ticket cache. This
* method assumes that login had happened already.
* The Subject field of this UserGroupInformation object is updated to have
* the new credentials.
* @throws IOException on a failure
*/
public synchronized void reloginFromTicketCache()
throws IOException {
if (!isSecurityEnabled() ||
user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
!isKrbTkt)
return;
LoginContext login = getLogin();
if (login == null) {
throw new IOException("login must be done first");
}
long now = System.currentTimeMillis();
if (!hasSufficientTimeElapsed(now)) {
return;
}
// register most recent relogin attempt
user.setLastLogin(now);
try {
LOG.info("Initiating logout for " + getUserName());
//clear up the kerberos state. But the tokens are not cleared! As per
//the Java kerberos login module code, only the kerberos credentials
//are cleared
login.logout();
//login and also update the subject field of this instance to
//have the new credentials (pass it to the LoginContext constructor)
login =
new LoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME,
getSubject());
LOG.info("Initiating re-login for " + getUserName());
login.login();
setLogin(login);
} catch (LoginException le) {
throw new IOException("Login failure for " + getUserName(), le);
}
}
/**
* Log a user in from a keytab file. Loads a user identity from a keytab
* file and login them in. This new user does not affect the currently
* logged-in user.
* @param user the principal name to load from the keytab
* @param path the path to the keytab file
* @throws IOException if the keytab file can't be read
*/
public synchronized
static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
String path
) throws IOException {
if (!isSecurityEnabled())
return UserGroupInformation.getCurrentUser();
String oldKeytabFile = null;
String oldKeytabPrincipal = null;
long start = 0;
try {
oldKeytabFile = keytabFile;
oldKeytabPrincipal = keytabPrincipal;
keytabFile = path;
keytabPrincipal = user;
Subject subject = new Subject();
LoginContext login =
new LoginContext(HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, subject);
start = System.currentTimeMillis();
login.login();
metrics.loginSuccess.inc(System.currentTimeMillis() - start);
UserGroupInformation newLoginUser = new UserGroupInformation(subject);
newLoginUser.setLogin(login);
newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
return newLoginUser;
} catch (LoginException le) {
if (start > 0) {
metrics.loginFailure.inc(System.currentTimeMillis() - start);
}
throw new IOException("Login failure for " + user + " from keytab " +
path, le);
} finally {
if(oldKeytabFile != null) keytabFile = oldKeytabFile;
if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal;
}
}
private boolean hasSufficientTimeElapsed(long now) {
if (now - user.getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
LOG.warn("Not attempting to re-login since the last re-login was " +
"attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+
" before.");
return false;
}
return true;
}
/**
* Did the login happen via keytab
* @return true or false
*/
public synchronized static boolean isLoginKeytabBased() {
return loginUser.isKeytab;
}
/**
* Create a user from a login name. It is intended to be used for remote
* users in RPC, since it won't have any credentials.
* @param user the full user principal name, must not be empty or null
* @return the UserGroupInformation for the remote user.
*/
public static UserGroupInformation createRemoteUser(String user) {
if (user == null || "".equals(user)) {
throw new IllegalArgumentException("Null user");
}
Subject subject = new Subject();
subject.getPrincipals().add(new User(user));
UserGroupInformation result = new UserGroupInformation(subject);
result.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
return result;
}
/**
* existing types of authentications' methods
*/
@InterfaceStability.Evolving
public static enum AuthenticationMethod {
SIMPLE,
KERBEROS,
TOKEN,
CERTIFICATE,
KERBEROS_SSL,
PROXY;
}
/**
* Create a proxy user using username of the effective user and the ugi of the
* real user.
* @param user
* @param realUser
* @return proxyUser ugi
*/
public static UserGroupInformation createProxyUser(String user,
UserGroupInformation realUser) {
if (user == null || "".equals(user)) {
throw new IllegalArgumentException("Null user");
}
if (realUser == null) {
throw new IllegalArgumentException("Null real user");
}
Subject subject = new Subject();
Set principals = subject.getPrincipals();
principals.add(new User(user));
principals.add(new RealUser(realUser));
UserGroupInformation result =new UserGroupInformation(subject);
result.setAuthenticationMethod(AuthenticationMethod.PROXY);
return result;
}
/**
* get RealUser (vs. EffectiveUser)
* @return realUser running over proxy user
*/
public UserGroupInformation getRealUser() {
for (RealUser p: subject.getPrincipals(RealUser.class)) {
return p.getRealUser();
}
return null;
}
/**
* This class is used for storing the groups for testing. It stores a local
* map that has the translation of usernames to groups.
*/
private static class TestingGroups extends Groups {
private final Map> userToGroupsMapping =
new HashMap>();
private TestingGroups() {
super(new org.apache.hadoop.conf.Configuration());
}
@Override
public List getGroups(String user) {
List result = userToGroupsMapping.get(user);
if (result == null) {
result = new ArrayList();
}
return result;
}
private void setUserGroups(String user, String[] groups) {
userToGroupsMapping.put(user, Arrays.asList(groups));
}
}
/**
* Create a UGI for testing HDFS and MapReduce
* @param user the full user principal name
* @param userGroups the names of the groups that the user belongs to
* @return a fake user for running unit tests
*/
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
public static UserGroupInformation createUserForTesting(String user,
String[] userGroups) {
ensureInitialized();
UserGroupInformation ugi = createRemoteUser(user);
// make sure that the testing object is setup
if (!(groups instanceof TestingGroups)) {
groups = new TestingGroups();
}
// add the user groups
((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
return ugi;
}
/**
* Create a proxy user UGI for testing HDFS and MapReduce
*
* @param user
* the full user principal name for effective user
* @param realUser
* UGI of the real user
* @param userGroups
* the names of the groups that the user belongs to
* @return a fake user for running unit tests
*/
@InterfaceAudience.LimitedPrivate( { "HDFS", "MapReduce" })
public static UserGroupInformation createProxyUserForTesting(String user,
UserGroupInformation realUser, String[] userGroups) {
ensureInitialized();
UserGroupInformation ugi = createProxyUser(user, realUser);
// make sure that the testing object is setup
if (!(groups instanceof TestingGroups)) {
groups = new TestingGroups();
}
// add the user groups
((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
return ugi;
}
/**
* Get the user's login name.
* @return the user's name up to the first '/' or '@'.
*/
public String getShortUserName() {
for (User p: subject.getPrincipals(User.class)) {
return p.getShortName();
}
return null;
}
/**
* Get the user's full principal name.
* @return the user's full principal name.
*/
public String getUserName() {
return user.getName();
}
/**
* Add a TokenIdentifier to this UGI. The TokenIdentifier has typically been
* authenticated by the RPC layer as belonging to the user represented by this
* UGI.
*
* @param tokenId
* tokenIdentifier to be added
* @return true on successful add of new tokenIdentifier
*/
public synchronized boolean addTokenIdentifier(TokenIdentifier tokenId) {
return subject.getPublicCredentials().add(tokenId);
}
/**
* Get the set of TokenIdentifiers belonging to this UGI
*
* @return the set of TokenIdentifiers belonging to this UGI
*/
public synchronized Set getTokenIdentifiers() {
return subject.getPublicCredentials(TokenIdentifier.class);
}
/**
* Add a token to this UGI
*
* @param token Token to be added
* @return true on successful add of new token
*/
public synchronized boolean addToken(Token extends TokenIdentifier> token) {
return subject.getPrivateCredentials().add(token);
}
/**
* Obtain the collection of tokens associated with this user.
*
* @return an unmodifiable collection of tokens associated with user
*/
public synchronized
Collection> getTokens() {
Set