HADOOP-6559. Makes the RPC client automatically re-login when the SASL connection setup fails. This is applicable only to keytab based logins. Contributed by Devaraj Das.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@910169 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Devaraj Das 2010-02-15 07:28:17 +00:00
parent 96a1477d02
commit 8b4b190cbd
3 changed files with 93 additions and 8 deletions

View File

@ -140,6 +140,10 @@ Trunk (unreleased changes)
HADOOP-6534. Trim whitespace from directory lists initializing HADOOP-6534. Trim whitespace from directory lists initializing
LocalDirAllocator. (Todd Lipcon via cdouglas) LocalDirAllocator. (Todd Lipcon via cdouglas)
HADOOP-6559. Makes the RPC client automatically re-login when the SASL
connection setup fails. This is applicable only to keytab based logins.
(Devaraj Das)
OPTIMIZATIONS OPTIMIZATIONS
BUG FIXES BUG FIXES

View File

@ -364,6 +364,35 @@ private synchronized void disposeSasl() {
} }
} }
private synchronized void setupSaslConnection(final InputStream in2,
final OutputStream out2)
throws javax.security.sasl.SaslException,IOException,InterruptedException {
try {
saslRpcClient = new SaslRpcClient(authMethod, token,
serverPrincipal);
saslRpcClient.saslConnect(in2, out2);
} catch (javax.security.sasl.SaslException je) {
if (authMethod == AuthMethod.KERBEROS &&
UserGroupInformation.isLoginKeytabBased()) {
//try re-login
UserGroupInformation.getCurrentUser().reloginFromKeytab();
//try setting up the connection again
try {
saslRpcClient = new SaslRpcClient(authMethod, token,
serverPrincipal);
saslRpcClient.saslConnect(in2, out2);
} catch (javax.security.sasl.SaslException jee) {
UserGroupInformation.
setLastUnsuccessfulAuthenticationAttemptTime
(System.currentTimeMillis());
LOG.warn("Couldn't setup connection for " +
UserGroupInformation.getCurrentUser().getUserName() +
" to " + serverPrincipal + " even after relogin.");
throw jee;
}
} else throw je;
}
}
/** Connect to the server and set up the I/O streams. It then sends /** Connect to the server and set up the I/O streams. It then sends
* a header to the server and starts * a header to the server and starts
* the connection thread that waits for responses. * the connection thread that waits for responses.
@ -410,10 +439,8 @@ private synchronized void setupIOstreams() throws InterruptedException {
} }
ticket.doAs(new PrivilegedExceptionAction<Object>() { ticket.doAs(new PrivilegedExceptionAction<Object>() {
@Override @Override
public Object run() throws IOException { public Object run() throws IOException, InterruptedException {
saslRpcClient = new SaslRpcClient(authMethod, token, setupSaslConnection(in2, out2);
serverPrincipal);
saslRpcClient.saslConnect(in2, out2);
return null; return null;
} }
}); });

View File

@ -130,6 +130,10 @@ public boolean logout() throws LoginException {
private static boolean useKerberos; private static boolean useKerberos;
/** Server-side groups fetching service */ /** Server-side groups fetching service */
private static Groups groups; private static Groups groups;
/** The last authentication time */
private static long lastUnsuccessfulAuthenticationAttemptTime;
public static final long MIN_TIME_BEFORE_RELOGIN = 10 * 60 * 1000L;
/**Environment variable pointing to the token cache file*/ /**Environment variable pointing to the token cache file*/
public static final String HADOOP_TOKEN_FILE_LOCATION = public static final String HADOOP_TOKEN_FILE_LOCATION =
@ -202,6 +206,8 @@ public static boolean isSecurityEnabled() {
private final Subject subject; private final Subject subject;
private static LoginContext login;
private static final String OS_LOGIN_MODULE_NAME; private static final String OS_LOGIN_MODULE_NAME;
private static final Class<? extends Principal> OS_PRINCIPAL_CLASS; private static final Class<? extends Principal> OS_PRINCIPAL_CLASS;
private static final boolean windows = private static final boolean windows =
@ -278,6 +284,7 @@ private static class HadoopConfiguration
static { static {
USER_KERBEROS_OPTIONS.put("doNotPrompt", "true"); USER_KERBEROS_OPTIONS.put("doNotPrompt", "true");
USER_KERBEROS_OPTIONS.put("useTicketCache", "true"); USER_KERBEROS_OPTIONS.put("useTicketCache", "true");
USER_KERBEROS_OPTIONS.put("renewTGT", "true");
String ticketCache = System.getenv("KRB5CCNAME"); String ticketCache = System.getenv("KRB5CCNAME");
if (ticketCache != null) { if (ticketCache != null) {
USER_KERBEROS_OPTIONS.put("ticketCache", ticketCache); USER_KERBEROS_OPTIONS.put("ticketCache", ticketCache);
@ -293,8 +300,6 @@ private static class HadoopConfiguration
KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true"); KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true"); KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true"); KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
KEYTAB_KERBEROS_OPTIONS.put("useTicketCache", "true");
KEYTAB_KERBEROS_OPTIONS.put("renewTGT", "true");
} }
private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN = private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN =
new AppConfigurationEntry(Krb5LoginModule.class.getName(), new AppConfigurationEntry(Krb5LoginModule.class.getName(),
@ -355,7 +360,6 @@ public static UserGroupInformation getCurrentUser() throws IOException {
static UserGroupInformation getLoginUser() throws IOException { static UserGroupInformation getLoginUser() throws IOException {
if (loginUser == null) { if (loginUser == null) {
try { try {
LoginContext login;
if (isSecurityEnabled()) { if (isSecurityEnabled()) {
login = new LoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME); login = new LoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME);
} else { } else {
@ -391,7 +395,7 @@ static void loginUserFromKeytab(String user,
keytabFile = path; keytabFile = path;
keytabPrincipal = user; keytabPrincipal = user;
try { try {
LoginContext login = login =
new LoginContext(HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME); new LoginContext(HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME);
login.login(); login.login();
loginUser = new UserGroupInformation(login.getSubject()); loginUser = new UserGroupInformation(login.getSubject());
@ -400,7 +404,57 @@ static void loginUserFromKeytab(String user,
path, le); path, le);
} }
} }
/**
* Re-Login a user in from a keytab file. Loads a user identity from a keytab
* file and login 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())
return;
if (login == null || keytabFile == null) {
throw new IOException("loginUserFromKeyTab must be done first");
}
if (System.currentTimeMillis() -lastUnsuccessfulAuthenticationAttemptTime <
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;
}
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.KEYTAB_KERBEROS_CONFIG_NAME,
getSubject());
LOG.info("Initiating re-login for " + keytabPrincipal);
login.login();
} catch (LoginException le) {
throw new IOException("Login failure for " + keytabPrincipal +
" from keytab " + keytabFile, le);
}
}
public synchronized static void
setLastUnsuccessfulAuthenticationAttemptTime(long time) {
lastUnsuccessfulAuthenticationAttemptTime = time;
}
public synchronized static boolean isLoginKeytabBased() {
return keytabFile != null;
}
/** /**
* Create a user from a login name. It is intended to be used for remote * 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. * users in RPC, since it won't have any credentials.