HBASE-11810 Access SSL Passwords through Credential Provider API (Larry McCay)
This commit is contained in:
parent
8c0e5dca75
commit
e5a5e968f1
|
@ -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,52 @@ 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) {
|
||||
LOG.debug(String.format("Config option \"%s\" was found through" +
|
||||
" the Configuration getPassword method.", alias));
|
||||
passwd = new String(p);
|
||||
}
|
||||
else {
|
||||
LOG.debug(String.format(
|
||||
"Config option \"%s\" was not found. Using provided default value",
|
||||
alias));
|
||||
passwd = defPass;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// this is a version of Hadoop where the credential
|
||||
//provider API doesn't exist yet
|
||||
LOG.debug(String.format(
|
||||
"Credential.getPassword method is not available." +
|
||||
" Falling back to configuration."));
|
||||
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"
|
||||
|
|
|
@ -18,9 +18,16 @@
|
|||
|
||||
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.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
@ -28,6 +35,8 @@ import org.junit.experimental.categories.Category;
|
|||
@Category(SmallTests.class)
|
||||
public class TestHBaseConfiguration {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(TestHBaseConfiguration.class);
|
||||
|
||||
@Test
|
||||
public void testGetIntDeprecated() {
|
||||
int VAL = 1, VAL2 = 2;
|
||||
|
@ -53,4 +62,268 @@ 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);
|
||||
|
||||
// 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) {
|
||||
LOG.error("Failed to load class:" + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
getCredentialMethod = loadMethod(hadoopCredentialEntryClz,
|
||||
HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME);
|
||||
} catch (Exception e1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hadoopClassesAvailable = true;
|
||||
LOG.info("Credential provider classes have been" +
|
||||
" loaded and initialized successfully through reflection.");
|
||||
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) {
|
||||
LOG.error("Failed to load the " + name + ": " + e);
|
||||
fail("no such method: " + name + " in " + clz.getCanonicalName());
|
||||
throw e;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to fetch the configured {@code List<CredentialProvider>}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<Object> getCredentialProviders(Configuration conf) {
|
||||
// Call CredentialProviderFactory.getProviders(Configuration)
|
||||
Object providersObj = null;
|
||||
try {
|
||||
providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory,
|
||||
conf);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
|
||||
": " + e);
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
|
||||
": " + e);
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
|
||||
": " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cast the Object to List<Object> (actually List<CredentialProvider>)
|
||||
try {
|
||||
return (List<Object>) 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<Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue