001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNull; 022import static org.junit.Assert.fail; 023 024import java.io.File; 025import java.io.IOException; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.util.List; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.testclassification.MiscTests; 031import org.apache.hadoop.hbase.testclassification.SmallTests; 032import org.junit.AfterClass; 033import org.junit.ClassRule; 034import org.junit.Test; 035import org.junit.experimental.categories.Category; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap; 040 041@Category({MiscTests.class, SmallTests.class}) 042public class TestHBaseConfiguration { 043 @ClassRule 044 public static final HBaseClassTestRule CLASS_RULE = 045 HBaseClassTestRule.forClass(TestHBaseConfiguration.class); 046 047 private static final Logger LOG = LoggerFactory.getLogger(TestHBaseConfiguration.class); 048 049 private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility(); 050 051 @AfterClass 052 public static void tearDown() throws IOException { 053 UTIL.cleanupTestDir(); 054 } 055 056 @Test 057 public void testSubset() { 058 Configuration conf = HBaseConfiguration.create(); 059 // subset is used in TableMapReduceUtil#initCredentials to support different security 060 // configurations between source and destination clusters, so we'll use that as an example 061 String prefix = "hbase.mapred.output."; 062 conf.set("hbase.security.authentication", "kerberos"); 063 conf.set("hbase.regionserver.kerberos.principal", "hbasesource"); 064 HBaseConfiguration.setWithPrefix(conf, prefix, 065 ImmutableMap.of( 066 "hbase.regionserver.kerberos.principal", "hbasedest", 067 "", "shouldbemissing") 068 .entrySet()); 069 070 Configuration subsetConf = HBaseConfiguration.subset(conf, prefix); 071 assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal")); 072 assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal")); 073 assertNull(subsetConf.get("hbase.security.authentication")); 074 assertNull(subsetConf.get("")); 075 076 Configuration mergedConf = HBaseConfiguration.create(conf); 077 HBaseConfiguration.merge(mergedConf, subsetConf); 078 079 assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal")); 080 assertEquals("kerberos", mergedConf.get("hbase.security.authentication")); 081 assertEquals("shouldbemissing", mergedConf.get(prefix)); 082 } 083 084 @Test 085 public void testGetPassword() throws Exception { 086 Configuration conf = HBaseConfiguration.create(); 087 conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file" 088 + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath()); 089 ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient(); 090 if (client.isHadoopCredentialProviderAvailable()) { 091 char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' }; 092 char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' }; 093 client.createEntry(conf, "ssl.keypass.alias", keyPass); 094 client.createEntry(conf, "ssl.storepass.alias", storePass); 095 096 String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null); 097 assertEquals(keypass, new String(keyPass)); 098 099 String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null); 100 assertEquals(storepass, new String(storePass)); 101 } 102 } 103 104 private static class ReflectiveCredentialProviderClient { 105 public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME = 106 "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory"; 107 public static final String 108 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders"; 109 110 public static final String HADOOP_CRED_PROVIDER_CLASS_NAME = 111 "org.apache.hadoop.security.alias.CredentialProvider"; 112 public static final String 113 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME = 114 "getCredentialEntry"; 115 public static final String 116 HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases"; 117 public static final String 118 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME = 119 "createCredentialEntry"; 120 public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush"; 121 122 public static final String HADOOP_CRED_ENTRY_CLASS_NAME = 123 "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry"; 124 public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME = 125 "getCredential"; 126 127 public static final String CREDENTIAL_PROVIDER_PATH = 128 "hadoop.security.credential.provider.path"; 129 130 private static Object hadoopCredProviderFactory = null; 131 private static Method getProvidersMethod = null; 132 private static Method getCredentialEntryMethod = null; 133 private static Method getCredentialMethod = null; 134 private static Method createCredentialEntryMethod = null; 135 private static Method flushMethod = null; 136 private static Boolean hadoopClassesAvailable = null; 137 138 /** 139 * Determine if we can load the necessary CredentialProvider classes. Only 140 * loaded the first time, so subsequent invocations of this method should 141 * return fast. 142 * 143 * @return True if the CredentialProvider classes/methods are available, 144 * false otherwise. 145 */ 146 private boolean isHadoopCredentialProviderAvailable() { 147 if (null != hadoopClassesAvailable) { 148 // Make sure everything is initialized as expected 149 if (hadoopClassesAvailable && null != getProvidersMethod 150 && null != hadoopCredProviderFactory 151 && null != getCredentialEntryMethod && null != getCredentialMethod) { 152 return true; 153 } else { 154 // Otherwise we failed to load it 155 return false; 156 } 157 } 158 159 hadoopClassesAvailable = false; 160 161 // Load Hadoop CredentialProviderFactory 162 Class<?> hadoopCredProviderFactoryClz; 163 try { 164 hadoopCredProviderFactoryClz = Class 165 .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME); 166 } catch (ClassNotFoundException e) { 167 return false; 168 } 169 // Instantiate Hadoop CredentialProviderFactory 170 try { 171 hadoopCredProviderFactory = 172 hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance(); 173 } catch (Exception e) { 174 return false; 175 } 176 177 try { 178 getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz, 179 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME, 180 Configuration.class); 181 // Load Hadoop CredentialProvider 182 Class<?> hadoopCredProviderClz; 183 hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME); 184 getCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 185 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class); 186 187 Method getAliasesMethod = 188 loadMethod(hadoopCredProviderClz, HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME); 189 190 createCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 191 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME, 192 String.class, char[].class); 193 194 flushMethod = loadMethod(hadoopCredProviderClz, 195 HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME); 196 197 // Load Hadoop CredentialEntry 198 Class<?> hadoopCredentialEntryClz; 199 try { 200 hadoopCredentialEntryClz = Class 201 .forName(HADOOP_CRED_ENTRY_CLASS_NAME); 202 } catch (ClassNotFoundException e) { 203 LOG.error("Failed to load class:" + e); 204 return false; 205 } 206 207 getCredentialMethod = loadMethod(hadoopCredentialEntryClz, 208 HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME); 209 } catch (Exception e1) { 210 return false; 211 } 212 213 hadoopClassesAvailable = true; 214 LOG.info("Credential provider classes have been" + 215 " loaded and initialized successfully through reflection."); 216 return true; 217 } 218 219 private Method loadMethod(Class<?> clz, String name, Class<?>... classes) 220 throws Exception { 221 Method method; 222 try { 223 method = clz.getMethod(name, classes); 224 } catch (SecurityException e) { 225 fail("security exception caught for: " + name + " in " + clz.getCanonicalName()); 226 throw e; 227 } catch (NoSuchMethodException e) { 228 LOG.error("Failed to load the " + name + ": " + e); 229 fail("no such method: " + name + " in " + clz.getCanonicalName()); 230 throw e; 231 } 232 return method; 233 } 234 235 /** 236 * Wrapper to fetch the configured {@code List<CredentialProvider>}s. 237 * 238 * @param conf 239 * Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined 240 * @return List of CredentialProviders, or null if they could not be loaded 241 */ 242 @SuppressWarnings("unchecked") 243 protected List<Object> getCredentialProviders(Configuration conf) { 244 // Call CredentialProviderFactory.getProviders(Configuration) 245 Object providersObj; 246 try { 247 providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory, 248 conf); 249 } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { 250 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 251 ": " + e); 252 return null; 253 } 254 255 // Cast the Object to List<Object> (actually List<CredentialProvider>) 256 try { 257 return (List<Object>) providersObj; 258 } catch (ClassCastException e) { 259 return null; 260 } 261 } 262 263 /** 264 * Create a CredentialEntry using the configured Providers. 265 * If multiple CredentialProviders are configured, the first will be used. 266 * 267 * @param conf 268 * Configuration for the CredentialProvider 269 * @param name 270 * CredentialEntry name (alias) 271 * @param credential 272 * The credential 273 */ 274 public void createEntry(Configuration conf, String name, char[] credential) 275 throws Exception { 276 if (!isHadoopCredentialProviderAvailable()) { 277 return; 278 } 279 280 List<Object> providers = getCredentialProviders(conf); 281 if (null == providers) { 282 throw new IOException("Could not fetch any CredentialProviders, " + 283 "is the implementation available?"); 284 } 285 286 Object provider = providers.get(0); 287 createEntryInProvider(provider, name, credential); 288 } 289 290 /** 291 * Create a CredentialEntry with the give name and credential in the 292 * credentialProvider. The credentialProvider argument must be an instance 293 * of Hadoop 294 * CredentialProvider. 295 * 296 * @param credentialProvider 297 * Instance of CredentialProvider 298 * @param name 299 * CredentialEntry name (alias) 300 * @param credential 301 * The credential to store 302 */ 303 private void createEntryInProvider(Object credentialProvider, 304 String name, char[] credential) throws Exception { 305 if (!isHadoopCredentialProviderAvailable()) { 306 return; 307 } 308 309 try { 310 createCredentialEntryMethod.invoke(credentialProvider, name, credential); 311 } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { 312 return; 313 } 314 315 flushMethod.invoke(credentialProvider); 316 } 317 } 318}