|
@@ -18,6 +18,8 @@
|
|
|
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_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_DEFAULT;
|
|
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES;
|
|
@@ -79,6 +81,7 @@ import org.apache.hadoop.security.token.TokenIdentifier;
|
|
|
import org.apache.hadoop.util.Shell;
|
|
|
import org.apache.hadoop.util.StringUtils;
|
|
|
import org.apache.hadoop.util.Time;
|
|
|
+
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
@@ -273,6 +276,29 @@ public class UserGroupInformation {
|
|
|
/** Min time (in seconds) before relogin for Kerberos */
|
|
|
private static long kerberosMinSecondsBeforeRelogin;
|
|
|
/** 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;
|
|
|
|
|
|
|
|
@@ -338,6 +364,15 @@ public class UserGroupInformation {
|
|
|
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.");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -351,7 +386,19 @@ public class UserGroupInformation {
|
|
|
public static void setConfiguration(Configuration conf) {
|
|
|
initialize(conf, true);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ @InterfaceAudience.Private
|
|
|
+ @VisibleForTesting
|
|
|
+ static void setEnableRenewThreadCreationForTest(boolean b) {
|
|
|
+ enableRenewThreadCreationForTest = b;
|
|
|
+ }
|
|
|
+
|
|
|
+ @InterfaceAudience.Private
|
|
|
+ @VisibleForTesting
|
|
|
+ static boolean getEnableRenewThreadCreationForTest() {
|
|
|
+ return enableRenewThreadCreationForTest;
|
|
|
+ }
|
|
|
+
|
|
|
@InterfaceAudience.Private
|
|
|
@VisibleForTesting
|
|
|
public static void reset() {
|
|
@@ -361,6 +408,7 @@ public class UserGroupInformation {
|
|
|
kerberosMinSecondsBeforeRelogin = 0;
|
|
|
setLoginUser(null);
|
|
|
HadoopKerberosName.setRules(null);
|
|
|
+ setEnableRenewThreadCreationForTest(false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -392,6 +440,7 @@ public class UserGroupInformation {
|
|
|
private final User user;
|
|
|
private final boolean isKeytab;
|
|
|
private final boolean isKrbTkt;
|
|
|
+ private final boolean isLoginExternal;
|
|
|
|
|
|
private static String OS_LOGIN_MODULE_NAME;
|
|
|
private static Class<? extends Principal> OS_PRINCIPAL_CLASS;
|
|
@@ -644,28 +693,28 @@ public class UserGroupInformation {
|
|
|
/**
|
|
|
* Create a UserGroupInformation for the given subject.
|
|
|
* 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
|
|
|
*/
|
|
|
UserGroupInformation(Subject subject) {
|
|
|
- this(subject, false);
|
|
|
+ this(subject, treatSubjectExternal);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Create a UGI from the given 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
|
|
|
* 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.user = subject.getPrincipals(User.class).iterator().next();
|
|
|
- if (externalKeyTab) {
|
|
|
- this.isKeytab = false;
|
|
|
- } else {
|
|
|
- this.isKeytab = KerberosUtil.hasKerberosKeyTab(subject);
|
|
|
- }
|
|
|
+
|
|
|
+ this.isKeytab = KerberosUtil.hasKerberosKeyTab(subject);
|
|
|
this.isKrbTkt = KerberosUtil.hasKerberosTicket(subject);
|
|
|
+ this.isLoginExternal = isLoginExternal;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -766,7 +815,7 @@ public class UserGroupInformation {
|
|
|
User ugiUser = new User(loginPrincipals.iterator().next().getName(),
|
|
|
AuthenticationMethod.KERBEROS, login);
|
|
|
loginSubject.getPrincipals().add(ugiUser);
|
|
|
- UserGroupInformation ugi = new UserGroupInformation(loginSubject);
|
|
|
+ UserGroupInformation ugi = new UserGroupInformation(loginSubject, false);
|
|
|
ugi.setLogin(login);
|
|
|
ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
|
|
|
return ugi;
|
|
@@ -782,7 +831,9 @@ public class UserGroupInformation {
|
|
|
/**
|
|
|
* 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 KerberosAuthException if the kerberos login fails
|
|
@@ -843,6 +894,10 @@ public class UserGroupInformation {
|
|
|
* Log in a user using the given subject
|
|
|
* @param subject the subject to use when logging in a user, or null to
|
|
|
* create a new subject.
|
|
|
+ *
|
|
|
+ * If subject is not null, the creator of subject is responsible for renewing
|
|
|
+ * credentials.
|
|
|
+ *
|
|
|
* @throws IOException if login fails
|
|
|
*/
|
|
|
@InterfaceAudience.Public
|
|
@@ -850,17 +905,25 @@ public class UserGroupInformation {
|
|
|
public synchronized
|
|
|
static void loginUserFromSubject(Subject subject) throws IOException {
|
|
|
ensureInitialized();
|
|
|
+ boolean externalSubject = false;
|
|
|
try {
|
|
|
if (subject == null) {
|
|
|
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 =
|
|
|
newLoginContext(authenticationMethod.getLoginAppName(),
|
|
|
subject, new HadoopConfiguration());
|
|
|
login.login();
|
|
|
- LOG.debug("Assuming keytab is managed externally since logged in from"
|
|
|
- + " subject.");
|
|
|
- UserGroupInformation realUser = new UserGroupInformation(subject, true);
|
|
|
+
|
|
|
+ UserGroupInformation realUser =
|
|
|
+ new UserGroupInformation(subject, externalSubject);
|
|
|
realUser.setLogin(login);
|
|
|
realUser.setAuthenticationMethod(authenticationMethod);
|
|
|
// If the HADOOP_PROXY_USER environment variable or property
|
|
@@ -959,11 +1022,23 @@ public class UserGroupInformation {
|
|
|
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*/
|
|
|
private void spawnAutoRenewalThreadForUserCreds() {
|
|
|
- if (!isSecurityEnabled()
|
|
|
- || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|
|
|
- || isKeytab) {
|
|
|
+ if (getEnableRenewThreadCreationForTest()) {
|
|
|
+ LOG.warn("Spawning thread to auto renew user credential since " +
|
|
|
+ " enableRenewThreadCreationForTest was set to true.");
|
|
|
+ } else if (!shouldRelogin() || isKeytab) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -1092,7 +1167,7 @@ public class UserGroupInformation {
|
|
|
start = Time.now();
|
|
|
login.login();
|
|
|
metrics.loginSuccess.add(Time.now() - start);
|
|
|
- loginUser = new UserGroupInformation(subject);
|
|
|
+ loginUser = new UserGroupInformation(subject, false);
|
|
|
loginUser.setLogin(login);
|
|
|
loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
|
|
|
} catch (LoginException le) {
|
|
@@ -1156,8 +1231,9 @@ public class UserGroupInformation {
|
|
|
public synchronized void checkTGTAndReloginFromKeytab() throws IOException {
|
|
|
if (!isSecurityEnabled()
|
|
|
|| user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|
|
|
- || !isKeytab)
|
|
|
+ || !isKeytab) {
|
|
|
return;
|
|
|
+ }
|
|
|
KerberosTicket tgt = getTGT();
|
|
|
if (tgt != null && !shouldRenewImmediatelyForTests &&
|
|
|
Time.now() < getRefreshTime(tgt)) {
|
|
@@ -1210,9 +1286,7 @@ public class UserGroupInformation {
|
|
|
@InterfaceAudience.Public
|
|
|
@InterfaceStability.Evolving
|
|
|
public synchronized void reloginFromKeytab() throws IOException {
|
|
|
- if (!isSecurityEnabled()
|
|
|
- || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|
|
|
- || !isKeytab) {
|
|
|
+ if (!shouldRelogin() || !isKeytab) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -1281,9 +1355,7 @@ public class UserGroupInformation {
|
|
|
@InterfaceAudience.Public
|
|
|
@InterfaceStability.Evolving
|
|
|
public synchronized void reloginFromTicketCache() throws IOException {
|
|
|
- if (!isSecurityEnabled()
|
|
|
- || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
|
|
|
- || !isKrbTkt) {
|
|
|
+ if (!shouldRelogin() || !isKrbTkt) {
|
|
|
return;
|
|
|
}
|
|
|
LoginContext login = getLogin();
|
|
@@ -1354,7 +1426,8 @@ public class UserGroupInformation {
|
|
|
start = Time.now();
|
|
|
login.login();
|
|
|
metrics.loginSuccess.add(Time.now() - start);
|
|
|
- UserGroupInformation newLoginUser = new UserGroupInformation(subject);
|
|
|
+ UserGroupInformation newLoginUser =
|
|
|
+ new UserGroupInformation(subject, false);
|
|
|
newLoginUser.setLogin(login);
|
|
|
newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
|
|
|
|
|
@@ -1427,7 +1500,7 @@ public class UserGroupInformation {
|
|
|
}
|
|
|
Subject subject = new Subject();
|
|
|
subject.getPrincipals().add(new User(user));
|
|
|
- UserGroupInformation result = new UserGroupInformation(subject);
|
|
|
+ UserGroupInformation result = new UserGroupInformation(subject, false);
|
|
|
result.setAuthenticationMethod(authMethod);
|
|
|
return result;
|
|
|
}
|
|
@@ -1504,7 +1577,7 @@ public class UserGroupInformation {
|
|
|
Set<Principal> principals = subject.getPrincipals();
|
|
|
principals.add(new User(user));
|
|
|
principals.add(new RealUser(realUser));
|
|
|
- UserGroupInformation result =new UserGroupInformation(subject);
|
|
|
+ UserGroupInformation result =new UserGroupInformation(subject, false);
|
|
|
result.setAuthenticationMethod(AuthenticationMethod.PROXY);
|
|
|
return result;
|
|
|
}
|