Skip to content

Commit

Permalink
HADOOP-13805. UGI.getCurrentUser() fails if user does not have a keyt…
Browse files Browse the repository at this point in the history
…ab associated. Contributed by Xiao Chen, Wei-Chiu Chuang, Yongjun Zhang.
  • Loading branch information
Yongjun Zhang committed Feb 17, 2017
1 parent 02c5494 commit 4c26c24
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 30 deletions.
Expand Up @@ -353,6 +353,17 @@ public class CommonConfigurationKeys extends CommonConfigurationKeysPublic {
public static final String HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS = public static final String HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS =
"hadoop.user.group.metrics.percentiles.intervals"; "hadoop.user.group.metrics.percentiles.intervals";


/* When creating UGI with UserGroupInformation(Subject), treat the passed
* subject external if set to true, and assume the owner of the subject
* should do the credential renewal.
*
* This is a temporary config to solve the compatibility issue with
* HADOOP-13558 and HADOOP-13805 fix, see the jiras for discussions.
*/
public static final String HADOOP_TREAT_SUBJECT_EXTERNAL_KEY =
"hadoop.treat.subject.external";
public static final boolean HADOOP_TREAT_SUBJECT_EXTERNAL_DEFAULT = false;

public static final String RPC_METRICS_QUANTILE_ENABLE = public static final String RPC_METRICS_QUANTILE_ENABLE =
"rpc.metrics.quantile.enable"; "rpc.metrics.quantile.enable";
public static final boolean RPC_METRICS_QUANTILE_ENABLE_DEFAULT = false; public static final boolean RPC_METRICS_QUANTILE_ENABLE_DEFAULT = false;
Expand Down
Expand Up @@ -18,6 +18,8 @@
package org.apache.hadoop.security; package org.apache.hadoop.security;


import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_TREAT_SUBJECT_EXTERNAL_KEY;
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_TREAT_SUBJECT_EXTERNAL_DEFAULT;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN_DEFAULT; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN_DEFAULT;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES;
Expand Down Expand Up @@ -79,6 +81,7 @@
import org.apache.hadoop.util.Shell; import org.apache.hadoop.util.Shell;
import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.util.StringUtils;
import org.apache.hadoop.util.Time; import org.apache.hadoop.util.Time;

import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


Expand Down Expand Up @@ -273,6 +276,29 @@ public static void reattachMetrics() {
/** Min time (in seconds) before relogin for Kerberos */ /** Min time (in seconds) before relogin for Kerberos */
private static long kerberosMinSecondsBeforeRelogin; private static long kerberosMinSecondsBeforeRelogin;
/** The configuration to use */ /** The configuration to use */

/*
* This config is a temporary one for backward compatibility.
* It means whether to treat the subject passed to
* UserGroupInformation(Subject) as external. If true,
* - no renewal thread will be created to do the renew credential
* - reloginFromKeytab() and reloginFromTicketCache will not renew
* credential.
* and it assumes that the owner of the subject to renew; if false, it means
* to retain the old behavior prior to fixing HADOOP-13558 and HADOOP-13805.
* The default is false.
*/
private static boolean treatSubjectExternal = false;

/*
* Some test need the renewal thread to be created even if it does
* UserGroupInformation.loginUserFromSubject(subject);
* The test code may set this variable to true via
* setEnableRenewThreadCreationForTest(boolean)
* method.
*/
private static boolean enableRenewThreadCreationForTest = false;

private static Configuration conf; private static Configuration conf;




Expand Down Expand Up @@ -338,6 +364,15 @@ private static synchronized void initialize(Configuration conf,
metrics.getGroupsQuantiles = getGroupsQuantiles; metrics.getGroupsQuantiles = getGroupsQuantiles;
} }
} }

treatSubjectExternal = conf.getBoolean(HADOOP_TREAT_SUBJECT_EXTERNAL_KEY,
HADOOP_TREAT_SUBJECT_EXTERNAL_DEFAULT);
if (treatSubjectExternal) {
LOG.info("Config " + HADOOP_TREAT_SUBJECT_EXTERNAL_KEY + " is set to "
+ "true, the owner of the subject passed to "
+ " UserGroupInformation(Subject) is supposed to renew the "
+ "credential.");
}
} }


/** /**
Expand All @@ -351,7 +386,19 @@ private static synchronized void initialize(Configuration conf,
public static void setConfiguration(Configuration conf) { public static void setConfiguration(Configuration conf) {
initialize(conf, true); initialize(conf, true);
} }


@InterfaceAudience.Private
@VisibleForTesting
static void setEnableRenewThreadCreationForTest(boolean b) {
enableRenewThreadCreationForTest = b;
}

@InterfaceAudience.Private
@VisibleForTesting
static boolean getEnableRenewThreadCreationForTest() {
return enableRenewThreadCreationForTest;
}

@InterfaceAudience.Private @InterfaceAudience.Private
@VisibleForTesting @VisibleForTesting
public static void reset() { public static void reset() {
Expand All @@ -361,6 +408,7 @@ public static void reset() {
kerberosMinSecondsBeforeRelogin = 0; kerberosMinSecondsBeforeRelogin = 0;
setLoginUser(null); setLoginUser(null);
HadoopKerberosName.setRules(null); HadoopKerberosName.setRules(null);
setEnableRenewThreadCreationForTest(false);
} }


/** /**
Expand Down Expand Up @@ -392,6 +440,7 @@ private static boolean isAuthenticationMethodEnabled(AuthenticationMethod method
private final User user; private final User user;
private final boolean isKeytab; private final boolean isKeytab;
private final boolean isKrbTkt; private final boolean isKrbTkt;
private final boolean isLoginExternal;


private static String OS_LOGIN_MODULE_NAME; private static String OS_LOGIN_MODULE_NAME;
private static Class<? extends Principal> OS_PRINCIPAL_CLASS; private static Class<? extends Principal> OS_PRINCIPAL_CLASS;
Expand Down Expand Up @@ -644,28 +693,28 @@ private void setLogin(LoginContext login) {
/** /**
* Create a UserGroupInformation for the given subject. * Create a UserGroupInformation for the given subject.
* This does not change the subject or acquire new credentials. * This does not change the subject or acquire new credentials.
*
* The creator of subject is responsible for renewing credentials.
* @param subject the user's subject * @param subject the user's subject
*/ */
UserGroupInformation(Subject subject) { UserGroupInformation(Subject subject) {
this(subject, false); this(subject, treatSubjectExternal);
} }


/** /**
* Create a UGI from the given subject. * Create a UGI from the given subject.
* @param subject the subject * @param subject the subject
* @param externalKeyTab if the subject's keytab is managed by the user. * @param isLoginExternal if the subject's keytab is managed by other UGI.
* Setting this to true will prevent UGI from attempting * Setting this to true will prevent UGI from attempting
* to login the keytab, or to renew it. * to login the keytab, or to renew it.
*/ */
private UserGroupInformation(Subject subject, final boolean externalKeyTab) { private UserGroupInformation(Subject subject, final boolean isLoginExternal) {
this.subject = subject; this.subject = subject;
this.user = subject.getPrincipals(User.class).iterator().next(); this.user = subject.getPrincipals(User.class).iterator().next();
if (externalKeyTab) {
this.isKeytab = false; this.isKeytab = KerberosUtil.hasKerberosKeyTab(subject);
} else {
this.isKeytab = KerberosUtil.hasKerberosKeyTab(subject);
}
this.isKrbTkt = KerberosUtil.hasKerberosTicket(subject); this.isKrbTkt = KerberosUtil.hasKerberosTicket(subject);
this.isLoginExternal = isLoginExternal;
} }


/** /**
Expand Down Expand Up @@ -766,7 +815,7 @@ public static UserGroupInformation getUGIFromTicketCache(
User ugiUser = new User(loginPrincipals.iterator().next().getName(), User ugiUser = new User(loginPrincipals.iterator().next().getName(),
AuthenticationMethod.KERBEROS, login); AuthenticationMethod.KERBEROS, login);
loginSubject.getPrincipals().add(ugiUser); loginSubject.getPrincipals().add(ugiUser);
UserGroupInformation ugi = new UserGroupInformation(loginSubject); UserGroupInformation ugi = new UserGroupInformation(loginSubject, false);
ugi.setLogin(login); ugi.setLogin(login);
ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS); ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
return ugi; return ugi;
Expand All @@ -782,7 +831,9 @@ public static UserGroupInformation getUGIFromTicketCache(
/** /**
* Create a UserGroupInformation from a Subject with Kerberos principal. * Create a UserGroupInformation from a Subject with Kerberos principal.
* *
* @param subject The KerberosPrincipal to use in UGI * @param subject The KerberosPrincipal to use in UGI.
* The creator of subject is responsible for
* renewing credentials.
* *
* @throws IOException * @throws IOException
* @throws KerberosAuthException if the kerberos login fails * @throws KerberosAuthException if the kerberos login fails
Expand Down Expand Up @@ -843,24 +894,36 @@ public static String trimLoginMethod(String userName) {
* Log in a user using the given subject * Log in a user using the given subject
* @param subject the subject to use when logging in a user, or null to * @param subject the subject to use when logging in a user, or null to
* create a new subject. * create a new subject.
*
* If subject is not null, the creator of subject is responsible for renewing
* credentials.
*
* @throws IOException if login fails * @throws IOException if login fails
*/ */
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Evolving
public synchronized public synchronized
static void loginUserFromSubject(Subject subject) throws IOException { static void loginUserFromSubject(Subject subject) throws IOException {
ensureInitialized(); ensureInitialized();
boolean externalSubject = false;
try { try {
if (subject == null) { if (subject == null) {
subject = new Subject(); subject = new Subject();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Treat subject external: " + treatSubjectExternal
+ ". When true, assuming keytab is managed extenally since "
+ " logged in from subject");
}
externalSubject = treatSubjectExternal;
} }
LoginContext login = LoginContext login =
newLoginContext(authenticationMethod.getLoginAppName(), newLoginContext(authenticationMethod.getLoginAppName(),
subject, new HadoopConfiguration()); subject, new HadoopConfiguration());
login.login(); login.login();
LOG.debug("Assuming keytab is managed externally since logged in from"
+ " subject."); UserGroupInformation realUser =
UserGroupInformation realUser = new UserGroupInformation(subject, true); new UserGroupInformation(subject, externalSubject);
realUser.setLogin(login); realUser.setLogin(login);
realUser.setAuthenticationMethod(authenticationMethod); realUser.setAuthenticationMethod(authenticationMethod);
// If the HADOOP_PROXY_USER environment variable or property // If the HADOOP_PROXY_USER environment variable or property
Expand Down Expand Up @@ -959,11 +1022,23 @@ private long getRefreshTime(KerberosTicket tgt) {
return start + (long) ((end - start) * TICKET_RENEW_WINDOW); return start + (long) ((end - start) * TICKET_RENEW_WINDOW);
} }


/**
* Should relogin if security is enabled using Kerberos, and
* the Subject is not owned by another UGI.
* @return true if this UGI should relogin
*/
private boolean shouldRelogin() {
return isSecurityEnabled()
&& user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS
&& !isLoginExternal;
}

/**Spawn a thread to do periodic renewals of kerberos credentials*/ /**Spawn a thread to do periodic renewals of kerberos credentials*/
private void spawnAutoRenewalThreadForUserCreds() { private void spawnAutoRenewalThreadForUserCreds() {
if (!isSecurityEnabled() if (getEnableRenewThreadCreationForTest()) {
|| user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS LOG.warn("Spawning thread to auto renew user credential since " +
|| isKeytab) { " enableRenewThreadCreationForTest was set to true.");
} else if (!shouldRelogin() || isKeytab) {
return; return;
} }


Expand Down Expand Up @@ -1092,7 +1167,7 @@ static void loginUserFromKeytab(String user,
start = Time.now(); start = Time.now();
login.login(); login.login();
metrics.loginSuccess.add(Time.now() - start); metrics.loginSuccess.add(Time.now() - start);
loginUser = new UserGroupInformation(subject); loginUser = new UserGroupInformation(subject, false);
loginUser.setLogin(login); loginUser.setLogin(login);
loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS); loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
} catch (LoginException le) { } catch (LoginException le) {
Expand Down Expand Up @@ -1156,8 +1231,9 @@ public void logoutUserFromKeytab() throws IOException {
public synchronized void checkTGTAndReloginFromKeytab() throws IOException { public synchronized void checkTGTAndReloginFromKeytab() throws IOException {
if (!isSecurityEnabled() if (!isSecurityEnabled()
|| user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|| !isKeytab) || !isKeytab) {
return; return;
}
KerberosTicket tgt = getTGT(); KerberosTicket tgt = getTGT();
if (tgt != null && !shouldRenewImmediatelyForTests && if (tgt != null && !shouldRenewImmediatelyForTests &&
Time.now() < getRefreshTime(tgt)) { Time.now() < getRefreshTime(tgt)) {
Expand Down Expand Up @@ -1210,9 +1286,7 @@ void fixKerberosTicketOrder() {
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Evolving
public synchronized void reloginFromKeytab() throws IOException { public synchronized void reloginFromKeytab() throws IOException {
if (!isSecurityEnabled() if (!shouldRelogin() || !isKeytab) {
|| user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|| !isKeytab) {
return; return;
} }


Expand Down Expand Up @@ -1281,9 +1355,7 @@ HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, getSubject(),
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Evolving
public synchronized void reloginFromTicketCache() throws IOException { public synchronized void reloginFromTicketCache() throws IOException {
if (!isSecurityEnabled() if (!shouldRelogin() || !isKrbTkt) {
|| user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|| !isKrbTkt) {
return; return;
} }
LoginContext login = getLogin(); LoginContext login = getLogin();
Expand Down Expand Up @@ -1354,7 +1426,8 @@ static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
start = Time.now(); start = Time.now();
login.login(); login.login();
metrics.loginSuccess.add(Time.now() - start); metrics.loginSuccess.add(Time.now() - start);
UserGroupInformation newLoginUser = new UserGroupInformation(subject); UserGroupInformation newLoginUser =
new UserGroupInformation(subject, false);
newLoginUser.setLogin(login); newLoginUser.setLogin(login);
newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS); newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);


Expand Down Expand Up @@ -1427,7 +1500,7 @@ public static UserGroupInformation createRemoteUser(String user, AuthMethod auth
} }
Subject subject = new Subject(); Subject subject = new Subject();
subject.getPrincipals().add(new User(user)); subject.getPrincipals().add(new User(user));
UserGroupInformation result = new UserGroupInformation(subject); UserGroupInformation result = new UserGroupInformation(subject, false);
result.setAuthenticationMethod(authMethod); result.setAuthenticationMethod(authMethod);
return result; return result;
} }
Expand Down Expand Up @@ -1504,7 +1577,7 @@ public static UserGroupInformation createProxyUser(String user,
Set<Principal> principals = subject.getPrincipals(); Set<Principal> principals = subject.getPrincipals();
principals.add(new User(user)); principals.add(new User(user));
principals.add(new RealUser(realUser)); principals.add(new RealUser(realUser));
UserGroupInformation result =new UserGroupInformation(subject); UserGroupInformation result =new UserGroupInformation(subject, false);
result.setAuthenticationMethod(AuthenticationMethod.PROXY); result.setAuthenticationMethod(AuthenticationMethod.PROXY);
return result; return result;
} }
Expand Down
Expand Up @@ -75,6 +75,7 @@ public void testAutoRenewalThreadRetryWithKdc() throws Exception {
SecurityUtil.setAuthenticationMethod( SecurityUtil.setAuthenticationMethod(
UserGroupInformation.AuthenticationMethod.KERBEROS, conf); UserGroupInformation.AuthenticationMethod.KERBEROS, conf);
UserGroupInformation.setConfiguration(conf); UserGroupInformation.setConfiguration(conf);
UserGroupInformation.setEnableRenewThreadCreationForTest(true);


LoginContext loginContext = null; LoginContext loginContext = null;
try { try {
Expand Down
Expand Up @@ -61,6 +61,7 @@
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;


import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_TREAT_SUBJECT_EXTERNAL_KEY;
import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
Expand Down Expand Up @@ -1020,8 +1021,7 @@ public void testExternalTokenFiles() throws Exception {
assertTrue(credsugiTokens.contains(token2)); assertTrue(credsugiTokens.contains(token2));
} }


@Test private void testCheckTGTAfterLoginFromSubjectHelper() throws Exception {
public void testCheckTGTAfterLoginFromSubject() throws Exception {
// security on, default is remove default realm // security on, default is remove default realm
SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf); SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
UserGroupInformation.setConfiguration(conf); UserGroupInformation.setConfiguration(conf);
Expand All @@ -1031,6 +1031,7 @@ public void testCheckTGTAfterLoginFromSubject() throws Exception {
KeyTab keytab = KeyTab.getInstance(); KeyTab keytab = KeyTab.getInstance();
subject.getPrivateCredentials().add(keytab); subject.getPrivateCredentials().add(keytab);
UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); UserGroupInformation ugi = UserGroupInformation.getCurrentUser();

ugi.doAs(new PrivilegedExceptionAction<Void>() { ugi.doAs(new PrivilegedExceptionAction<Void>() {
@Override @Override
public Void run() throws IOException { public Void run() throws IOException {
Expand All @@ -1042,6 +1043,17 @@ public Void run() throws IOException {
}); });
} }


@Test(expected = KerberosAuthException.class)
public void testCheckTGTAfterLoginFromSubject() throws Exception {
testCheckTGTAfterLoginFromSubjectHelper();
}

@Test
public void testCheckTGTAfterLoginFromSubjectFix() throws Exception {
conf.setBoolean(HADOOP_TREAT_SUBJECT_EXTERNAL_KEY, true);
testCheckTGTAfterLoginFromSubjectHelper();
}

@Test @Test
public void testGetNextRetryTime() throws Exception { public void testGetNextRetryTime() throws Exception {
GenericTestUtils.setLogLevel(UserGroupInformation.LOG, Level.DEBUG); GenericTestUtils.setLogLevel(UserGroupInformation.LOG, Level.DEBUG);
Expand Down

0 comments on commit 4c26c24

Please sign in to comment.