You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ga...@apache.org on 2011/03/05 02:10:08 UTC

svn commit: r1078225 - in /hbase/trunk: ./ src/main/java/org/apache/hadoop/hbase/master/ src/main/java/org/apache/hadoop/hbase/regionserver/ src/main/java/org/apache/hadoop/hbase/security/ src/main/resources/ src/test/java/org/apache/hadoop/hbase/secur...

Author: garyh
Date: Sat Mar  5 01:10:07 2011
New Revision: 1078225

URL: http://svn.apache.org/viewvc?rev=1078225&view=rev
Log:
HBASE-3582  Allow HMaster and HRegionServer to login from keytab when running on secure Hadoop

Modified:
    hbase/trunk/CHANGES.txt
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java
    hbase/trunk/src/main/resources/hbase-default.xml
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java

Modified: hbase/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Sat Mar  5 01:10:07 2011
@@ -136,6 +136,8 @@ Release 0.90.2 - Unreleased
                log4j.properties
    HBASE-3591  completebulkload doesn't honor generic -D options
    HBASE-3594  Rest server fails because of missing asm jar
+   HBASE-3582  Allow HMaster and HRegionServer to login from keytab
+               when on secure Hadoop
 
   IMPROVEMENTS
    HBASE-3542  MultiGet methods in Thrift

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java Sat Mar  5 01:10:07 2011
@@ -75,6 +75,7 @@ import org.apache.hadoop.hbase.master.ha
 import org.apache.hadoop.hbase.master.metrics.MasterMetrics;
 import org.apache.hadoop.hbase.regionserver.HRegion;
 import org.apache.hadoop.hbase.replication.regionserver.Replication;
+import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.InfoServer;
 import org.apache.hadoop.hbase.util.Pair;
@@ -203,6 +204,10 @@ implements HMasterInterface, HMasterRegi
       0); // this is a DNC w/o high priority handlers
     this.address = new HServerAddress(rpcServer.getListenerAddress());
 
+    // initialize server principal (if using secure Hadoop)
+    User.login(conf, "hbase.master.keytab.file",
+        "hbase.master.kerberos.principal", this.address.getHostname());
+
     // set the thread name now we have an address
     setName(MASTER + "-" + this.address);
 

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java Sat Mar  5 01:10:07 2011
@@ -118,6 +118,7 @@ import org.apache.hadoop.hbase.regionser
 import org.apache.hadoop.hbase.regionserver.wal.HLog;
 import org.apache.hadoop.hbase.regionserver.wal.WALObserver;
 import org.apache.hadoop.hbase.replication.regionserver.Replication;
+import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.CompressionTest;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
@@ -344,6 +345,10 @@ public class HRegionServer implements HR
       throw new NullPointerException("Server address cannot be null; "
           + "hbase-958 debugging");
     }
+
+    // login the server principal (if using secure Hadoop)
+    User.login(conf, "hbase.regionserver.keytab.file",
+        "hbase.regionserver.kerberos.principal", serverInfo.getHostname());
   }
 
   private static final int NORMAL_QOS = 0;

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java
URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java Sat Mar  5 01:10:07 2011
@@ -26,6 +26,7 @@ import org.apache.hadoop.security.UserGr
 
 import java.io.IOException;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.security.PrivilegedAction;
@@ -96,7 +97,7 @@ public abstract class User {
   /**
    * Returns the {@code User} instance within current execution context.
    */
-  public static User getCurrent() {
+  public static User getCurrent() throws IOException {
     if (IS_SECURE_HADOOP) {
       return new SecureHadoopUser();
     } else {
@@ -118,6 +119,31 @@ public abstract class User {
     return HadoopUser.createUserForTesting(conf, name, groups);
   }
 
+  /**
+   * Log in the current process using the given configuration keys for the
+   * credential file and login principal.
+   *
+   * <p><strong>This is only applicable when
+   * running on secure Hadoop</strong> -- see
+   * {@link org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String)}.
+   * On regular Hadoop (without security features), this will safely be ignored.
+   * </p>
+   *
+   * @param conf The configuration data to use
+   * @param fileConfKey Property key used to configure path to the credential file
+   * @param principalConfKey Property key used to configure login principal
+   * @param localhost Current hostname to use in any credentials
+   * @throws IOException underlying exception from SecurityUtil.login() call
+   */
+  public static void login(Configuration conf, String fileConfKey,
+      String principalConfKey, String localhost) throws IOException {
+    if (IS_SECURE_HADOOP) {
+      SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
+    } else {
+      HadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
+    }
+  }
+
   /* Concrete implementations */
 
   /**
@@ -129,7 +155,14 @@ public abstract class User {
   private static class HadoopUser extends User {
 
     private HadoopUser() {
-      ugi = (UserGroupInformation) callStatic("getCurrentUGI");
+      try {
+        ugi = (UserGroupInformation) callStatic("getCurrentUGI");
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception HadoopUser<init>");
+      }
     }
 
     private HadoopUser(UserGroupInformation ugi) {
@@ -143,30 +176,46 @@ public abstract class User {
 
     @Override
     public <T> T runAs(PrivilegedAction<T> action) {
-      UserGroupInformation previous =
-          (UserGroupInformation) callStatic("getCurrentUGI");
-      if (ugi != null) {
-        callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
-            new Object[]{ugi});
-      }
-      T result = action.run();
-      callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
-          new Object[]{previous});
+      T result = null;
+      UserGroupInformation previous = null;
+      try {
+        previous = (UserGroupInformation) callStatic("getCurrentUGI");
+        try {
+          if (ugi != null) {
+            callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
+                new Object[]{ugi});
+          }
+          result = action.run();
+        } finally {
+          callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
+              new Object[]{previous});
+        }
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception in runAs()");
+      }
       return result;
     }
 
     @Override
     public <T> T runAs(PrivilegedExceptionAction<T> action)
         throws IOException, InterruptedException {
-      UserGroupInformation previous =
-          (UserGroupInformation) callStatic("getCurrentUGI");
-      if (ugi != null) {
-        callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
-            new Object[]{ugi});
-      }
       T result = null;
       try {
-        result = action.run();
+        UserGroupInformation previous =
+            (UserGroupInformation) callStatic("getCurrentUGI");
+        try {
+          if (ugi != null) {
+            callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
+                new Object[]{ugi});
+          }
+          result = action.run();
+        } finally {
+          callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
+              new Object[]{previous});
+        }
       } catch (Exception e) {
         if (e instanceof IOException) {
           throw (IOException)e;
@@ -177,9 +226,6 @@ public abstract class User {
         } else {
           throw new UndeclaredThrowableException(e, "Unknown exception in runAs()");
         }
-      } finally {
-        callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
-            new Object[]{previous});
       }
       return result;
     }
@@ -199,14 +245,22 @@ public abstract class User {
         conf.set("hadoop.job.ugi", newUser.toString());
         return new HadoopUser(newUser);
       } catch (ClassNotFoundException cnfe) {
-        LOG.error("UnixUserGroupInformation not found, is this secure Hadoop?", cnfe);
+        throw new RuntimeException(
+            "UnixUserGroupInformation not found, is this secure Hadoop?", cnfe);
       } catch (NoSuchMethodException nsme) {
-        LOG.error("No valid constructor found for UnixUserGroupInformation!", nsme);
+        throw new RuntimeException(
+            "No valid constructor found for UnixUserGroupInformation!", nsme);
+      } catch (RuntimeException re) {
+        throw re;
       } catch (Exception e) {
-        LOG.error("Error instantiating new UnixUserGroupInformation", e);
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception instantiating new UnixUserGroupInformation");
       }
+    }
 
-      return null;
+    public static void login(Configuration conf, String fileConfKey,
+        String principalConfKey, String localhost) throws IOException {
+      LOG.info("Skipping login, not running on secure Hadoop");
     }
   }
 
@@ -216,8 +270,17 @@ public abstract class User {
    * 0.20 and versions 0.21 and above.
    */
   private static class SecureHadoopUser extends User {
-    private SecureHadoopUser() {
-      ugi = (UserGroupInformation) callStatic("getCurrentUser");
+    private SecureHadoopUser() throws IOException {
+      try {
+        ugi = (UserGroupInformation) callStatic("getCurrentUser");
+      } catch (IOException ioe) {
+        throw ioe;
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception getting current secure user");
+      }
     }
 
     private SecureHadoopUser(UserGroupInformation ugi) {
@@ -226,54 +289,149 @@ public abstract class User {
 
     @Override
     public String getShortName() {
-      return (String)call(ugi, "getShortUserName", null, null);
+      try {
+        return (String)call(ugi, "getShortUserName", null, null);
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected error getting user short name");
+      }
     }
 
     @Override
     public <T> T runAs(PrivilegedAction<T> action) {
-      return (T) call(ugi, "doAs", new Class[]{PrivilegedAction.class},
-          new Object[]{action});
+      try {
+        return (T) call(ugi, "doAs", new Class[]{PrivilegedAction.class},
+            new Object[]{action});
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception in runAs()");
+      }
     }
 
     @Override
     public <T> T runAs(PrivilegedExceptionAction<T> action)
         throws IOException, InterruptedException {
-      return (T) call(ugi, "doAs",
-          new Class[]{PrivilegedExceptionAction.class},
-          new Object[]{action});
+      try {
+        return (T) call(ugi, "doAs",
+            new Class[]{PrivilegedExceptionAction.class},
+            new Object[]{action});
+      } catch (IOException ioe) {
+        throw ioe;
+      } catch (InterruptedException ie) {
+        throw ie;
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unexpected exception in runAs(PrivilegedExceptionAction)");
+      }
     }
 
     public static User createUserForTesting(Configuration conf,
         String name, String[] groups) {
-      return new SecureHadoopUser(
-          (UserGroupInformation)callStatic("createUserForTesting",
-              new Class[]{String.class, String[].class},
-              new Object[]{name, groups})
-      );
+      try {
+        return new SecureHadoopUser(
+            (UserGroupInformation)callStatic("createUserForTesting",
+                new Class[]{String.class, String[].class},
+                new Object[]{name, groups})
+        );
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Error creating secure test user");
+      }
+    }
+
+    public static void login(Configuration conf, String fileConfKey,
+        String principalConfKey, String localhost) throws IOException {
+      // check for SecurityUtil class
+      try {
+        Class c = Class.forName("org.apache.hadoop.security.SecurityUtil");
+        Class[] types = new Class[]{
+            Configuration.class, String.class, String.class, String.class };
+        Object[] args = new Object[]{
+            conf, fileConfKey, principalConfKey, localhost };
+        call(c, null, "login", types, args);
+      } catch (ClassNotFoundException cnfe) {
+        throw new RuntimeException("Unable to login using " +
+            "org.apache.hadoop.security.Security.login(). SecurityUtil class " +
+            "was not found!  Is this a version of secure Hadoop?", cnfe);
+      } catch (IOException ioe) {
+        throw ioe;
+      } catch (RuntimeException re) {
+        throw re;
+      } catch (Exception e) {
+        throw new UndeclaredThrowableException(e,
+            "Unhandled exception in User.login()");
+      }
     }
   }
 
   /* Reflection helper methods */
-  private static Object callStatic(String methodName) {
+  private static Object callStatic(String methodName) throws Exception {
     return call(null, methodName, null, null);
   }
 
   private static Object callStatic(String methodName, Class[] types,
-      Object[] args) {
+      Object[] args) throws Exception {
     return call(null, methodName, types, args);
   }
 
   private static Object call(UserGroupInformation instance, String methodName,
-      Class[] types, Object[] args) {
+      Class[] types, Object[] args) throws Exception {
+    return call(UserGroupInformation.class, instance, methodName, types, args);
+  }
+
+  private static <T> Object call(Class<T> clazz, T instance, String methodName,
+      Class[] types, Object[] args) throws Exception {
     try {
-      Method m = UserGroupInformation.class.getMethod(methodName, types);
+      Method m = clazz.getMethod(methodName, types);
       return m.invoke(instance, args);
+    } catch (IllegalArgumentException arge) {
+      LOG.fatal("Constructed invalid call. class="+clazz.getName()+
+          " method=" + methodName + " types=" + stringify(types), arge);
+      throw arge;
     } catch (NoSuchMethodException nsme) {
-      LOG.fatal("Can't find method "+methodName+" in UserGroupInformation!",
-          nsme);
-    } catch (Exception e) {
-      LOG.fatal("Error calling method "+methodName, e);
+      throw new IllegalArgumentException(
+          "Can't find method "+methodName+" in "+clazz.getName()+"!", nsme);
+    } catch (InvocationTargetException ite) {
+      // unwrap the underlying exception and rethrow
+      if (ite.getTargetException() != null) {
+        if (ite.getTargetException() instanceof Exception) {
+          throw (Exception)ite.getTargetException();
+        } else if (ite.getTargetException() instanceof Error) {
+          throw (Error)ite.getTargetException();
+        }
+      }
+      throw new UndeclaredThrowableException(ite,
+          "Unknown exception invoking "+clazz.getName()+"."+methodName+"()");
+    } catch (IllegalAccessException iae) {
+      throw new IllegalArgumentException(
+          "Denied access calling "+clazz.getName()+"."+methodName+"()", iae);
+    } catch (SecurityException se) {
+      LOG.fatal("SecurityException calling method. class="+clazz.getName()+
+          " method=" + methodName + " types=" + stringify(types), se);
+      throw se;
+    }
+  }
+
+  private static String stringify(Class[] classes) {
+    StringBuilder buf = new StringBuilder();
+    if (classes != null) {
+      for (Class c : classes) {
+        if (buf.length() > 0) {
+          buf.append(",");
+        }
+        buf.append(c.getName());
+      }
+    } else {
+      buf.append("NULL");
     }
-    return null;
+    return buf.toString();
   }
 }

Modified: hbase/trunk/src/main/resources/hbase-default.xml
URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/resources/hbase-default.xml?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/src/main/resources/hbase-default.xml (original)
+++ hbase/trunk/src/main/resources/hbase-default.xml Sat Mar  5 01:10:07 2011
@@ -459,6 +459,45 @@
     used for client / server RPC call marshalling.
     </description>
   </property>
+
+  <!-- The following properties configure authentication information for
+       HBase processes when using Kerberos security.  There are no default
+       values, included here for documentation purposes -->
+  <property>
+    <name>hbase.master.keytab.file</name>
+    <value></value>
+    <description>Full path to the kerberos keytab file to use for logging in
+    the configured HMaster server principal.
+    </description>
+  </property>
+  <property>
+    <name>hbase.master.kerberos.principal</name>
+    <value></value>
+    <description>Ex. "hbase/_HOST@EXAMPLE.COM".  The kerberos principal name
+    that should be used to run the HMaster process.  The principal name should
+    be in the form: user/hostname@DOMAIN.  If "_HOST" is used as the hostname
+    portion, it will be replaced with the actual hostname of the running
+    instance.
+    </description>
+  </property>
+  <property>
+    <name>hbase.regionserver.keytab.file</name>
+    <value></value>
+    <description>Full path to the kerberos keytab file to use for logging in
+    the configured HRegionServer server principal.
+    </description>
+  </property>
+  <property>
+    <name>hbase.regionserver.kerberos.principal</name>
+    <value></value>
+    <description>Ex. "hbase/_HOST@EXAMPLE.COM".  The kerberos principal name
+    that should be used to run the HRegionServer process.  The principal name
+    should be in the form: user/hostname@DOMAIN.  If "_HOST" is used as the
+    hostname portion, it will be replaced with the actual hostname of the
+    running instance.  An entry for this principal must exist in the file
+    specified in hbase.regionserver.keytab.file
+    </description>
+  </property>
   <property>
     <name>zookeeper.session.timeout</name>
     <value>180000</value>

Modified: hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java
URL: http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java?rev=1078225&r1=1078224&r2=1078225&view=diff
==============================================================================
--- hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java (original)
+++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java Sat Mar  5 01:10:07 2011
@@ -25,6 +25,7 @@ import org.apache.hadoop.conf.Configurat
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.security.PrivilegedAction;
 import java.security.PrivilegedExceptionAction;
 
@@ -42,10 +43,10 @@ public class TestUser {
   public void testRunAs() throws Exception {
     Configuration conf = HBaseConfiguration.create();
     final User user = User.createUserForTesting(conf, "testuser", new String[]{"foo"});
-    final PrivilegedAction<String> action = new PrivilegedAction<String>(){
-      public String run() {
-        User u = User.getCurrent();
-        return u.getName();
+    final PrivilegedExceptionAction<String> action = new PrivilegedExceptionAction<String>(){
+      public String run() throws IOException {
+          User u = User.getCurrent();
+          return u.getName();
       }
     };
 
@@ -68,8 +69,8 @@ public class TestUser {
     assertEquals("User name in runAs() should match", "testuser", username);
 
     // verify that nested contexts work
-    user2.runAs(new PrivilegedAction(){
-      public Object run() {
+    user2.runAs(new PrivilegedExceptionAction(){
+      public Object run() throws IOException, InterruptedException{
         String nestedName = user.runAs(action);
         assertEquals("Nest name should match nested user", "testuser", nestedName);
         assertEquals("Current name should match current user",