diff --git hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseConfiguration.java hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseConfiguration.java index 9a0b38b..996fe27 100644 --- hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseConfiguration.java +++ hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseConfiguration.java @@ -17,6 +17,9 @@ */ package org.apache.hadoop.hbase; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map.Entry; import org.apache.commons.logging.Log; @@ -160,6 +163,44 @@ public class HBaseConfiguration extends Configuration { } } + /** + * Get the password from the Configuration instance using the + * getPassword method if it exists. If not, then fall back to the + * general get method for configuration elements. + * @param conf configuration instance for accessing the passwords + * @param alias the name of the password element + * @param defPass the default password + * @return String password or default password + * @throws IOException + */ + public static String getPassword(Configuration conf, String alias, + String defPass) throws IOException { + String passwd = null; + try { + Method m = Configuration.class.getMethod("getPassword", String.class); + char[] p = (char[]) m.invoke(conf, alias); + if (p != null) { + passwd = new String(p); + } + else { + passwd = defPass; + } + } catch (NoSuchMethodException e) { + // this is a version of Hadoop where the credential + //provider API doesn't exist yet + passwd = conf.get(alias, defPass); + } catch (SecurityException e) { + throw new IOException(e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new IOException(e.getMessage(), e); + } catch (IllegalArgumentException e) { + throw new IOException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new IOException(e.getMessage(), e); + } + return passwd; + } + /** For debugging. Dump configurations to system output as xml format. * Master and RS configurations can also be dumped using * http services. e.g. "curl http://master:16010/dump" diff --git hbase-common/src/test/java/org/apache/hadoop/hbase/TestHBaseConfiguration.java hbase-common/src/test/java/org/apache/hadoop/hbase/TestHBaseConfiguration.java index 94eac02..25346c1 100644 --- hbase-common/src/test/java/org/apache/hadoop/hbase/TestHBaseConfiguration.java +++ hbase-common/src/test/java/org/apache/hadoop/hbase/TestHBaseConfiguration.java @@ -18,8 +18,13 @@ package org.apache.hadoop.hbase; - import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; import org.apache.hadoop.conf.Configuration; import org.junit.Test; @@ -27,7 +32,6 @@ import org.junit.experimental.categories.Category; @Category(SmallTests.class) public class TestHBaseConfiguration { - @Test public void testGetIntDeprecated() { int VAL = 1, VAL2 = 2; @@ -53,4 +57,266 @@ public class TestHBaseConfiguration { assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); } + @Test + public void testGetPassword() throws Exception { + Configuration conf = HBaseConfiguration.create(); + conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, + "jceks://file/tmp/foo.jks"); + ReflectiveCredentialProviderClient client = + new ReflectiveCredentialProviderClient(); + if (client.isHadoopCredentialProviderAvailable()) { + char[] keyPass = {'k', 'e', 'y', 'p', 'a', 's', 's'}; + char[] storePass = {'s', 't', 'o', 'r', 'e', 'p', 'a', 's', 's'}; + client.createEntry(conf, "ssl.keypass.alias", keyPass); + client.createEntry(conf, "ssl.storepass.alias", storePass); + + String keypass = HBaseConfiguration.getPassword( + conf, "ssl.keypass.alias", null); + assertEquals(keypass, new String(keyPass)); + + String storepass = HBaseConfiguration.getPassword( + conf, "ssl.storepass.alias", null); + assertEquals(storepass, new String(storePass)); + } + } + + private static class ReflectiveCredentialProviderClient { + public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME = + "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory"; + public static final String + HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders"; + + public static final String HADOOP_CRED_PROVIDER_CLASS_NAME = + "org.apache.hadoop.security.alias.CredentialProvider"; + public static final String + HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME = + "getCredentialEntry"; + public static final String + HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases"; + public static final String + HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME = + "createCredentialEntry"; + public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush"; + + public static final String HADOOP_CRED_ENTRY_CLASS_NAME = + "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry"; + public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME = + "getCredential"; + + public static final String CREDENTIAL_PROVIDER_PATH = + "hadoop.security.credential.provider.path"; + + private static Object hadoopCredProviderFactory = null; + private static Method getProvidersMethod = null; + private static Method getAliasesMethod = null; + private static Method getCredentialEntryMethod = null; + private static Method getCredentialMethod = null; + private static Method createCredentialEntryMethod = null; + private static Method flushMethod = null; + private static Boolean hadoopClassesAvailable = null; + + /** + * Determine if we can load the necessary CredentialProvider classes. Only + * loaded the first time, so subsequent invocations of this method should + * return fast. + * + * @return True if the CredentialProvider classes/methods are available, + * false otherwise. + */ + private boolean isHadoopCredentialProviderAvailable() { + if (null != hadoopClassesAvailable) { + // Make sure everything is initialized as expected + if (hadoopClassesAvailable && null != getProvidersMethod + && null != hadoopCredProviderFactory + && null != getCredentialEntryMethod && null != getCredentialMethod) { + return true; + } else { + // Otherwise we failed to load it + return false; + } + } + + hadoopClassesAvailable = false; + + // Load Hadoop CredentialProviderFactory + Class hadoopCredProviderFactoryClz = null; + try { + hadoopCredProviderFactoryClz = Class + .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME); + } catch (ClassNotFoundException e) { + return false; + } + // Instantiate Hadoop CredentialProviderFactory + try { + hadoopCredProviderFactory = hadoopCredProviderFactoryClz.newInstance(); + } catch (InstantiationException e) { + return false; + } catch (IllegalAccessException e) { + return false; + } + + try { + getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz, + HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME, + Configuration.class); + } catch (Exception e1) { + e1.printStackTrace(); + } + + try { + // Load Hadoop CredentialProvider + Class hadoopCredProviderClz = null; + hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME); + getCredentialEntryMethod = loadMethod(hadoopCredProviderClz, + HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class); + + getAliasesMethod = loadMethod(hadoopCredProviderClz, + HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME); + + createCredentialEntryMethod = loadMethod(hadoopCredProviderClz, + HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME, + String.class, char[].class); + + flushMethod = loadMethod(hadoopCredProviderClz, + HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME); + + // Load Hadoop CredentialEntry + Class hadoopCredentialEntryClz = null; + try { + hadoopCredentialEntryClz = Class + .forName(HADOOP_CRED_ENTRY_CLASS_NAME); + } catch (ClassNotFoundException e) { + return false; + } + + getCredentialMethod = loadMethod(hadoopCredentialEntryClz, + HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME); + } catch (Exception e1) { + e1.printStackTrace(); + return false; + } + + hadoopClassesAvailable = true; + return true; + + } + + private Method loadMethod(Class clz, String name, Class... classes) + throws Exception { + Method method = null; + try { + method = clz.getMethod(name, classes); + } catch (SecurityException e) { + fail("security exception caught for: " + name + " in " + + clz.getCanonicalName()); + throw e; + } catch (NoSuchMethodException e) { + fail("no such method: " + name + " in " + clz.getCanonicalName()); + throw e; + } + return method; + } + + /** + * Wrapper to fetch the configured {@code List}s. + * + * @param conf + * Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined + * @return List of CredentialProviders, or null if they could not be loaded + */ + @SuppressWarnings("unchecked") + protected List getCredentialProviders(Configuration conf) { + // Call CredentialProviderFactory.getProviders(Configuration) + Object providersObj = null; + try { + providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory, + conf); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return null; + } catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } catch (InvocationTargetException e) { + e.printStackTrace(); + return null; + } + + // Cast the Object to List (actually List) + try { + return (List) providersObj; + } catch (ClassCastException e) { + return null; + } + } + + /** + * Create a CredentialEntry using the configured Providers. + * If multiple CredentialProviders are configured, the first will be used. + * + * @param conf + * Configuration for the CredentialProvider + * @param name + * CredentialEntry name (alias) + * @param credential + * The credential + */ + public void createEntry(Configuration conf, String name, char[] credential) + throws Exception { + + if (!isHadoopCredentialProviderAvailable()) { + return; + } + + List providers = getCredentialProviders(conf); + if (null == providers) { + throw new IOException("Could not fetch any CredentialProviders, " + + "is the implementation available?"); + } + + Object provider = providers.get(0); + createEntryInProvider(provider, name, credential); + } + + /** + * Create a CredentialEntry with the give name and credential in the + * credentialProvider. The credentialProvider argument must be an instance + * of Hadoop + * CredentialProvider. + * + * @param credentialProvider + * Instance of CredentialProvider + * @param name + * CredentialEntry name (alias) + * @param credential + * The credential to store + */ + private void createEntryInProvider(Object credentialProvider, + String name, char[] credential) throws Exception { + + if (!isHadoopCredentialProviderAvailable()) { + return; + } + + try { + createCredentialEntryMethod.invoke(credentialProvider, name, credential); + } catch (IllegalArgumentException e) { + return; + } catch (IllegalAccessException e) { + return; + } catch (InvocationTargetException e) { + return; + } + + try { + flushMethod.invoke(credentialProvider); + } catch (IllegalArgumentException e) { + throw e; + } catch (IllegalAccessException e) { + throw e; + } catch (InvocationTargetException e) { + throw e; + } + } + } } diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java hbase-server/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index e6f13d6..64faa14 100644 --- hbase-server/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ hbase-server/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -191,8 +191,10 @@ public class RESTServer implements Constants { if(conf.getBoolean(REST_SSL_ENABLED, false)) { SslSelectChannelConnector sslConnector = new SslSelectChannelConnector(); String keystore = conf.get(REST_SSL_KEYSTORE_STORE); - String password = conf.get(REST_SSL_KEYSTORE_PASSWORD); - String keyPassword = conf.get(REST_SSL_KEYSTORE_KEYPASSWORD, password); + String password = HBaseConfiguration.getPassword(conf, + REST_SSL_KEYSTORE_PASSWORD, null); + String keyPassword = HBaseConfiguration.getPassword(conf, + REST_SSL_KEYSTORE_KEYPASSWORD, password); sslConnector.setKeystore(keystore); sslConnector.setPassword(password); sslConnector.setKeyPassword(keyPassword);