blob: ffa94ba2d59f7604dd10b74b5c2bf24f4b32c3bf [file] [log] [blame]
/**
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.io.File;
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.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.testclassification.MiscTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
@Category({MiscTests.class, SmallTests.class})
public class TestHBaseConfiguration {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestHBaseConfiguration.class);
private static final Logger LOG = LoggerFactory.getLogger(TestHBaseConfiguration.class);
private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility();
@AfterClass
public static void tearDown() throws IOException {
UTIL.cleanupTestDir();
}
@Test
public void testSubset() {
Configuration conf = HBaseConfiguration.create();
// subset is used in TableMapReduceUtil#initCredentials to support different security
// configurations between source and destination clusters, so we'll use that as an example
String prefix = "hbase.mapred.output.";
conf.set("hbase.security.authentication", "kerberos");
conf.set("hbase.regionserver.kerberos.principal", "hbasesource");
HBaseConfiguration.setWithPrefix(conf, prefix,
ImmutableMap.of(
"hbase.regionserver.kerberos.principal", "hbasedest",
"", "shouldbemissing")
.entrySet());
Configuration subsetConf = HBaseConfiguration.subset(conf, prefix);
assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal"));
assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal"));
assertNull(subsetConf.get("hbase.security.authentication"));
assertNull(subsetConf.get(""));
Configuration mergedConf = HBaseConfiguration.create(conf);
HBaseConfiguration.merge(mergedConf, subsetConf);
assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal"));
assertEquals("kerberos", mergedConf.get("hbase.security.authentication"));
assertEquals("shouldbemissing", mergedConf.get(prefix));
}
@Test
public void testGetPassword() throws Exception {
Configuration conf = HBaseConfiguration.create();
conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file"
+ new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath());
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));
}
}
@Test
public void testSecurityConfCaseInsensitive() {
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.security.authentication", "kerberos");
Assert.assertTrue(User.isHBaseSecurityEnabled(conf));
conf.set("hbase.security.authentication", "KERBEROS");
Assert.assertTrue(User.isHBaseSecurityEnabled(conf));
conf.set("hbase.security.authentication", "KERBeros");
Assert.assertTrue(User.isHBaseSecurityEnabled(conf));
}
@Test
public void testGetConfigOfShortcircuitRead() throws Exception {
Configuration conf = HBaseConfiguration.create();
Configuration.addDefaultResource("hdfs-scr-disabled.xml");
assertEquals("hdfs-scr-disabled.xml",
conf.getPropertySources("dfs.client.read.shortcircuit")[0]);
assertEquals("false", conf.get("dfs.client.read.shortcircuit"));
assertNull(conf.get("dfs.domain.socket.path"));
Configuration.addDefaultResource("hdfs-scr-enabled.xml");
assertEquals("hdfs-scr-enabled.xml",
conf.getPropertySources("dfs.client.read.shortcircuit")[0]);
assertEquals("hdfs-scr-enabled.xml",
conf.getPropertySources("dfs.domain.socket.path")[0]);
assertEquals("true", conf.get("dfs.client.read.shortcircuit"));
assertEquals("/var/lib/hadoop-hdfs/dn_socket", conf.get("dfs.domain.socket.path"));
}
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 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;
try {
hadoopCredProviderFactoryClz = Class
.forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME);
} catch (ClassNotFoundException e) {
return false;
}
// Instantiate Hadoop CredentialProviderFactory
try {
hadoopCredProviderFactory =
hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
return false;
}
try {
getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz,
HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME,
Configuration.class);
// Load Hadoop CredentialProvider
Class<?> hadoopCredProviderClz;
hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME);
getCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class);
Method 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;
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;
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;
try {
providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory,
conf);
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException 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 | InvocationTargetException | IllegalAccessException e) {
return;
}
flushMethod.invoke(credentialProvider);
}
}
}