HBASE-3582 Allow HMaster and HRegionServer to login from keytab when running on secure Hadoop
git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1078225 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
2a530e891c
commit
4d28e95a61
|
@ -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
|
||||
|
|
|
@ -75,6 +75,7 @@ import org.apache.hadoop.hbase.master.handler.TableModifyFamilyHandler;
|
|||
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, HMasterRegionInterface, MasterServices, Server {
|
|||
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);
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ import org.apache.hadoop.hbase.regionserver.metrics.RegionServerMetrics;
|
|||
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 HRegionInterface, HBaseRPCErrorHandler,
|
|||
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;
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.apache.hadoop.security.UserGroupInformation;
|
|||
|
||||
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() {
|
||||
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");
|
||||
T result = null;
|
||||
UserGroupInformation previous = null;
|
||||
try {
|
||||
previous = (UserGroupInformation) callStatic("getCurrentUGI");
|
||||
try {
|
||||
if (ugi != null) {
|
||||
callStatic("setCurrentUser", new Class[]{UserGroupInformation.class},
|
||||
new Object[]{ugi});
|
||||
}
|
||||
T result = action.run();
|
||||
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 {
|
||||
T result = null;
|
||||
try {
|
||||
UserGroupInformation previous =
|
||||
(UserGroupInformation) callStatic("getCurrentUGI");
|
||||
try {
|
||||
if (ugi != null) {
|
||||
callStatic("setCurrentUGI", new Class[]{UserGroupInformation.class},
|
||||
new Object[]{ugi});
|
||||
}
|
||||
T result = null;
|
||||
try {
|
||||
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() {
|
||||
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() {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
Class[] types, Object[] args) throws Exception {
|
||||
return call(UserGroupInformation.class, instance, methodName, types, args);
|
||||
}
|
||||
return null;
|
||||
|
||||
private static <T> Object call(Class<T> clazz, T instance, String methodName,
|
||||
Class[] types, Object[] args) throws Exception {
|
||||
try {
|
||||
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) {
|
||||
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 buf.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.apache.hadoop.conf.Configuration;
|
|||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
|
||||
|
@ -42,8 +43,8 @@ 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() {
|
||||
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",
|
||||
|
|
Loading…
Reference in New Issue