diff --git a/CHANGES.txt b/CHANGES.txt index bcd323ade5d..43c4f060a80 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -567,6 +567,7 @@ Release 0.90.5 - Unreleased (Gaojinchao) HBASE-4294 HLogSplitter sleeps with 1-second granularity (todd) HBASE-4270 IOE ignored during flush-on-close causes dataloss + HBASE-4180 HBase should check the isSecurityEnabled flag before login IMPROVEMENT HBASE-4205 Enhance HTable javadoc (Eric Charles) diff --git a/src/main/java/org/apache/hadoop/hbase/security/User.java b/src/main/java/org/apache/hadoop/hbase/security/User.java index 8002c89516d..d90f2c72211 100644 --- a/src/main/java/org/apache/hadoop/hbase/security/User.java +++ b/src/main/java/org/apache/hadoop/hbase/security/User.java @@ -22,12 +22,13 @@ package org.apache.hadoop.hbase.security; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.util.Methods; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapreduce.Job; 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; import java.security.PrivilegedExceptionAction; @@ -43,15 +44,13 @@ import org.apache.commons.logging.Log; * {@link org.apache.hadoop.security.UserGroupInformation} currently needed by * HBase, but can be extended as needs change. *

- * - *

- * 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. - *

*/ public abstract class User { + /** + * Flag to differentiate between API-incompatible changes to + * {@link org.apache.hadoop.security.UserGroupInformation} between vanilla + * Hadoop 0.20.x and secure Hadoop 0.20+. + */ private static boolean IS_SECURE_HADOOP = true; static { try { @@ -144,6 +143,20 @@ public abstract class User { } } + /** + * Returns whether or not Kerberos authentication is configured. For + * non-secure Hadoop, this always returns false. + * For secure Hadoop, it will return the value from + * {@code UserGroupInformation.isSecurityEnabled()}. + */ + public static boolean isSecurityEnabled() { + if (IS_SECURE_HADOOP) { + return SecureHadoopUser.isSecurityEnabled(); + } else { + return HadoopUser.isSecurityEnabled(); + } + } + /* Concrete implementations */ /** @@ -230,6 +243,7 @@ public abstract class User { return result; } + /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */ public static User createUserForTesting(Configuration conf, String name, String[] groups) { try { @@ -258,10 +272,20 @@ public abstract class User { } } + /** + * No-op since we're running on a version of Hadoop that doesn't support + * logins. + * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String) + */ public static void login(Configuration conf, String fileConfKey, String principalConfKey, String localhost) throws IOException { LOG.info("Skipping login, not running on secure Hadoop"); } + + /** Always returns {@code false}. */ + public static boolean isSecurityEnabled() { + return false; + } } /** @@ -331,6 +355,7 @@ public abstract class User { } } + /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */ public static User createUserForTesting(Configuration conf, String name, String[] groups) { try { @@ -347,27 +372,55 @@ public abstract class User { } } + /** + * Obtain credentials for the current process using the configured + * Kerberos keytab file and principal. + * @see User#login(org.apache.hadoop.conf.Configuration, String, String, String) + * + * @param conf the Configuration to use + * @param fileConfKey Configuration property key used to store the path + * to the keytab file + * @param principalConfKey Configuration property key used to store the + * principal name to login as + * @param localhost the local hostname + */ public static void login(Configuration conf, String fileConfKey, String principalConfKey, String localhost) throws IOException { - // check for SecurityUtil class + if (isSecurityEnabled()) { + // 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 }; + Methods.call(c, null, "login", types, args); + } catch (ClassNotFoundException cnfe) { + throw new RuntimeException("Unable to login using " + + "org.apache.hadoop.security.SecurityUtil.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()"); + } + } + } + + /** + * Returns the result of {@code UserGroupInformation.isSecurityEnabled()}. + */ + public static boolean isSecurityEnabled() { 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; + return (Boolean)callStatic("isSecurityEnabled"); } catch (RuntimeException re) { throw re; } catch (Exception e) { throw new UndeclaredThrowableException(e, - "Unhandled exception in User.login()"); + "Unexpected exception calling UserGroupInformation.isSecurityEnabled()"); } } } @@ -384,54 +437,7 @@ public abstract class User { private static Object call(UserGroupInformation instance, String methodName, Class[] types, Object[] args) throws Exception { - return call(UserGroupInformation.class, instance, methodName, types, args); - } - - private static Object call(Class 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(); + return Methods.call(UserGroupInformation.class, instance, methodName, types, + args); } } diff --git a/src/main/java/org/apache/hadoop/hbase/util/Classes.java b/src/main/java/org/apache/hadoop/hbase/util/Classes.java index 9313da8a660..2b353e737b3 100644 --- a/src/main/java/org/apache/hadoop/hbase/util/Classes.java +++ b/src/main/java/org/apache/hadoop/hbase/util/Classes.java @@ -1,3 +1,23 @@ +/* + * Copyright 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.util; /** @@ -41,4 +61,18 @@ public class Classes { return valueType; } + public 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(); + } } diff --git a/src/main/java/org/apache/hadoop/hbase/util/Methods.java b/src/main/java/org/apache/hadoop/hbase/util/Methods.java new file mode 100644 index 00000000000..a6f48352b73 --- /dev/null +++ b/src/main/java/org/apache/hadoop/hbase/util/Methods.java @@ -0,0 +1,65 @@ +/* + * Copyright 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.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class Methods { + private static Log LOG = LogFactory.getLog(Methods.class); + + public static Object call(Class 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=" + Classes.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=" + Classes.stringify(types), se); + throw se; + } + } +}