You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by st...@apache.org on 2010/11/09 17:56:22 UTC

svn commit: r1033089 - in /hbase/trunk/src: main/java/org/apache/hadoop/hbase/security/ main/java/org/apache/hadoop/hbase/security/User.java test/java/org/apache/hadoop/hbase/security/ test/java/org/apache/hadoop/hbase/security/TestUser.java

Author: stack
Date: Tue Nov  9 16:56:22 2010
New Revision: 1033089

URL: http://svn.apache.org/viewvc?rev=1033089&view=rev
Log:
HBASE-3194 HBase should run on both secure and vanilla versions of Hadoop 0.20 -- forgot to add classes

Added:
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java

Added: 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=1033089&view=auto
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java (added)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/security/User.java Tue Nov  9 16:56:22 2010
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hbase.security;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import org.apache.commons.logging.Log;
+
+/**
+ * Wrapper to abstract out usage of user and group information in HBase.
+ *
+ * <p>
+ * This class provides a common interface for interacting with user and group
+ * information across changing APIs in different versions of Hadoop.  It only
+ * provides access to the common set of functionality in
+ * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by
+ * HBase, but can be extended as needs change.
+ * </p>
+ *
+ * <p>
+ * Note: this class does not attempt to support any of the Kerberos
+ * authentication methods exposed in security-enabled Hadoop (for the moment
+ * at least), as they're not yet needed.  Properly supporting
+ * authentication is left up to implementation in secure HBase.
+ * </p>
+ */
+public abstract class User {
+  private static boolean IS_SECURE_HADOOP = true;
+  static {
+    try {
+      UserGroupInformation.class.getMethod("isSecurityEnabled");
+    } catch (NoSuchMethodException nsme) {
+      IS_SECURE_HADOOP = false;
+    }
+  }
+  private static Log LOG = LogFactory.getLog(User.class);
+  protected UserGroupInformation ugi;
+
+  /**
+   * Returns the full user name.  For Kerberos principals this will include
+   * the host and realm portions of the principal name.
+   * @return
+   */
+  public String getName() {
+    return ugi.getUserName();
+  }
+
+  /**
+   * Returns the shortened version of the user name -- the portion that maps
+   * to an operating system user name.
+   * @return
+   */
+  public abstract String getShortName();
+
+  /**
+   * Executes the given action within the context of this user.
+   */
+  public abstract <T> T runAs(PrivilegedAction<T> action);
+
+  /**
+   * Executes the given action within the context of this user.
+   */
+  public abstract <T> T runAs(PrivilegedExceptionAction<T> action)
+      throws IOException, InterruptedException;
+
+  public String toString() {
+    return ugi.toString();
+  }
+
+  /**
+   * Returns the {@code User} instance within current execution context.
+   */
+  public static User getCurrent() {
+    if (IS_SECURE_HADOOP) {
+      return new SecureHadoopUser();
+    } else {
+      return new HadoopUser();
+    }
+  }
+
+  /**
+   * Generates a new {@code User} instance specifically for use in test code.
+   * @param name the full username
+   * @param groups the group names to which the test user will belong
+   * @return a new <code>User</code> instance
+   */
+  public static User createUserForTesting(Configuration conf,
+      String name, String[] groups) {
+    if (IS_SECURE_HADOOP) {
+      return SecureHadoopUser.createUserForTesting(conf, name, groups);
+    }
+    return HadoopUser.createUserForTesting(conf, name, groups);
+  }
+
+  /* Concrete implementations */
+
+  /**
+   * Bridges {@link User} calls to invocations of the appropriate methods
+   * in {@link org.apache.hadoop.security.UserGroupInformation} in regular
+   * Hadoop 0.20 (ASF Hadoop and other versions without the backported security
+   * features).
+   */
+  private static class HadoopUser extends User {
+
+    private HadoopUser() {
+      ugi = (UserGroupInformation) callStatic("getCurrentUGI");
+    }
+
+    private HadoopUser(UserGroupInformation ugi) {
+      this.ugi = ugi;
+    }
+
+    @Override
+    public String getShortName() {
+      return ugi.getUserName();
+    }
+
+    @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});
+      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();
+      } catch (Exception e) {
+        if (e instanceof IOException) {
+          throw (IOException)e;
+        } else if (e instanceof InterruptedException) {
+          throw (InterruptedException)e;
+        } else if (e instanceof RuntimeException) {
+          throw (RuntimeException)e;
+        } else {
+          throw new UndeclaredThrowableException(e, "Unknown exception in runAs()");
+        }
+      } finally {
+        callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
+            new Object[]{previous});
+      }
+      return result;
+    }
+
+    public static User createUserForTesting(Configuration conf,
+        String name, String[] groups) {
+      try {
+        Class c = Class.forName("org.apache.hadoop.security.UnixUserGroupInformation");
+        Constructor constructor = c.getConstructor(String.class, String[].class);
+        if (constructor == null) {
+          throw new NullPointerException(
+             );
+        }
+        UserGroupInformation newUser =
+            (UserGroupInformation)constructor.newInstance(name, groups);
+        // set user in configuration -- hack for regular hadoop
+        conf.set("hadoop.job.ugi", newUser.toString());
+        return new HadoopUser(newUser);
+      } catch (ClassNotFoundException cnfe) {
+        LOG.error("UnixUserGroupInformation not found, is this secure Hadoop?", cnfe);
+      } catch (NoSuchMethodException nsme) {
+        LOG.error("No valid constructor found for UnixUserGroupInformation!", nsme);
+      } catch (Exception e) {
+        LOG.error("Error instantiating new UnixUserGroupInformation", e);
+      }
+
+      return null;
+    }
+  }
+
+  /**
+   * Bridges {@code User} invocations to underlying calls to
+   * {@link org.apache.hadoop.security.UserGroupInformation} for secure Hadoop
+   * 0.20 and versions 0.21 and above.
+   */
+  private static class SecureHadoopUser extends User {
+    private SecureHadoopUser() {
+      ugi = (UserGroupInformation) callStatic("getCurrentUser");
+    }
+
+    private SecureHadoopUser(UserGroupInformation ugi) {
+      this.ugi = ugi;
+    }
+
+    @Override
+    public String getShortName() {
+      return (String)call(ugi, "getShortUserName", null, null);
+    }
+
+    @Override
+    public <T> T runAs(PrivilegedAction<T> action) {
+      return (T) call(ugi, "doAs", new Class[]{PrivilegedAction.class},
+          new Object[]{action});
+    }
+
+    @Override
+    public <T> T runAs(PrivilegedExceptionAction<T> action)
+        throws IOException, InterruptedException {
+      return (T) call(ugi, "doAs",
+          new Class[]{PrivilegedExceptionAction.class},
+          new Object[]{action});
+    }
+
+    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})
+      );
+    }
+  }
+
+  /* Reflection helper methods */
+  private static Object callStatic(String methodName) {
+    return call(null, methodName, null, null);
+  }
+
+  private static Object callStatic(String methodName, Class[] types,
+      Object[] args) {
+    return call(null, methodName, types, args);
+  }
+
+  private static Object call(UserGroupInformation instance, String methodName,
+      Class[] types, Object[] args) {
+    try {
+      Method m = UserGroupInformation.class.getMethod(methodName, types);
+      return m.invoke(instance, args);
+    } catch (NoSuchMethodException nsme) {
+      LOG.fatal("Can't find method "+methodName+" in UserGroupInformation!",
+          nsme);
+    } catch (Exception e) {
+      LOG.fatal("Error calling method "+methodName, e);
+    }
+    return null;
+  }
+}

Added: 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=1033089&view=auto
==============================================================================
--- hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java (added)
+++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/security/TestUser.java Tue Nov  9 16:56:22 2010
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.security;
+
+import static org.junit.Assert.*;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.junit.Test;
+
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+
+public class TestUser {
+  @Test
+  public void testBasicAttributes() throws Exception {
+    Configuration conf = HBaseConfiguration.create();
+    User user = User.createUserForTesting(conf, "simple", new String[]{"foo"});
+    assertEquals("Username should match", "simple", user.getName());
+    assertEquals("Short username should match", "simple", user.getShortName());
+    // don't test shortening of kerberos names because regular Hadoop doesn't support them
+  }
+
+  @Test
+  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();
+      }
+    };
+
+    String username = user.runAs(action);
+    assertEquals("Current user within runAs() should match",
+        "testuser", username);
+
+    // ensure the next run is correctly set
+    User user2 = User.createUserForTesting(conf, "testuser2", new String[]{"foo"});
+    String username2 = user2.runAs(action);
+    assertEquals("Second username should match second user",
+        "testuser2", username2);
+
+    // check the exception version
+    username = user.runAs(new PrivilegedExceptionAction<String>(){
+      public String run() throws Exception {
+        return User.getCurrent().getName();
+      }
+    });
+    assertEquals("User name in runAs() should match", "testuser", username);
+
+    // verify that nested contexts work
+    user2.runAs(new PrivilegedAction(){
+      public Object run() {
+        String nestedName = user.runAs(action);
+        assertEquals("Nest name should match nested user", "testuser", nestedName);
+        assertEquals("Current name should match current user",
+            "testuser2", User.getCurrent().getName());
+        return null;
+      }
+    });
+  }
+}