You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by dd...@apache.org on 2010/02/15 08:28:18 UTC

svn commit: r910169 - in /hadoop/common/trunk: CHANGES.txt src/java/org/apache/hadoop/ipc/Client.java src/java/org/apache/hadoop/security/UserGroupInformation.java

Author: ddas
Date: Mon Feb 15 07:28:17 2010
New Revision: 910169

URL: http://svn.apache.org/viewvc?rev=910169&view=rev
Log:
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.

Modified:
    hadoop/common/trunk/CHANGES.txt
    hadoop/common/trunk/src/java/org/apache/hadoop/ipc/Client.java
    hadoop/common/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java

Modified: hadoop/common/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/CHANGES.txt?rev=910169&r1=910168&r2=910169&view=diff
==============================================================================
--- hadoop/common/trunk/CHANGES.txt (original)
+++ hadoop/common/trunk/CHANGES.txt Mon Feb 15 07:28:17 2010
@@ -140,6 +140,10 @@
     HADOOP-6534. Trim whitespace from directory lists initializing
     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
 
   BUG FIXES

Modified: hadoop/common/trunk/src/java/org/apache/hadoop/ipc/Client.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/ipc/Client.java?rev=910169&r1=910168&r2=910169&view=diff
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/ipc/Client.java (original)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/ipc/Client.java Mon Feb 15 07:28:17 2010
@@ -364,6 +364,35 @@
       }
     }
     
+    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
      * a header to the server and starts
      * the connection thread that waits for responses.
@@ -410,10 +439,8 @@
           }
           ticket.doAs(new PrivilegedExceptionAction<Object>() {
             @Override
-            public Object run() throws IOException {
-              saslRpcClient = new SaslRpcClient(authMethod, token,
-                  serverPrincipal);
-              saslRpcClient.saslConnect(in2, out2);
+            public Object run() throws IOException, InterruptedException {
+              setupSaslConnection(in2, out2);
               return null;
             }
           });

Modified: hadoop/common/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java?rev=910169&r1=910168&r2=910169&view=diff
==============================================================================
--- hadoop/common/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java (original)
+++ hadoop/common/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java Mon Feb 15 07:28:17 2010
@@ -130,6 +130,10 @@
   private static boolean useKerberos;
   /** Server-side groups fetching service */
   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*/
   public static final String HADOOP_TOKEN_FILE_LOCATION = 
@@ -202,6 +206,8 @@
 
   private final Subject subject;
   
+  private static LoginContext login;
+  
   private static final String OS_LOGIN_MODULE_NAME;
   private static final Class<? extends Principal> OS_PRINCIPAL_CLASS;
   private static final boolean windows = 
@@ -278,6 +284,7 @@
     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);
@@ -293,8 +300,6 @@
       KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
       KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "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 =
       new AppConfigurationEntry(Krb5LoginModule.class.getName(),
@@ -355,7 +360,6 @@
   static UserGroupInformation getLoginUser() throws IOException {
     if (loginUser == null) {
       try {
-        LoginContext login;
         if (isSecurityEnabled()) {
           login = new LoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME);
         } else {
@@ -391,7 +395,7 @@
     keytabFile = path;
     keytabPrincipal = user;
     try {
-      LoginContext login = 
+      login = 
         new LoginContext(HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME);
       login.login();
       loginUser = new UserGroupInformation(login.getSubject());
@@ -400,7 +404,57 @@
                             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
    * users in RPC, since it won't have any credentials.