1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package org.apache.hadoop.hbase; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.fail; 24 25 import java.io.File; 26 import java.io.IOException; 27 import java.lang.reflect.InvocationTargetException; 28 import java.lang.reflect.Method; 29 import java.util.List; 30 31 import org.apache.commons.logging.Log; 32 import org.apache.commons.logging.LogFactory; 33 import org.apache.hadoop.conf.Configuration; 34 import org.apache.hadoop.hbase.testclassification.SmallTests; 35 import org.junit.AfterClass; 36 import org.junit.Test; 37 import org.junit.experimental.categories.Category; 38 39 import com.google.common.collect.ImmutableMap; 40 41 @Category(SmallTests.class) 42 public class TestHBaseConfiguration { 43 44 private static final Log LOG = LogFactory.getLog(TestHBaseConfiguration.class); 45 46 private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility(); 47 48 @AfterClass tearDown()49 public static void tearDown() throws IOException { 50 UTIL.cleanupTestDir(); 51 } 52 53 @Test testGetIntDeprecated()54 public void testGetIntDeprecated() { 55 int VAL = 1, VAL2 = 2; 56 String NAME = "foo"; 57 String DEPRECATED_NAME = "foo.deprecated"; 58 59 Configuration conf = HBaseConfiguration.create(); 60 conf.setInt(NAME, VAL); 61 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 62 63 conf = HBaseConfiguration.create(); 64 conf.setInt(DEPRECATED_NAME, VAL); 65 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 66 67 conf = HBaseConfiguration.create(); 68 conf.setInt(DEPRECATED_NAME, VAL); 69 conf.setInt(NAME, VAL); 70 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 71 72 conf = HBaseConfiguration.create(); 73 conf.setInt(DEPRECATED_NAME, VAL); 74 conf.setInt(NAME, VAL2); // deprecated value will override this 75 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 76 } 77 78 @Test testSubset()79 public void testSubset() throws Exception { 80 Configuration conf = HBaseConfiguration.create(); 81 // subset is used in TableMapReduceUtil#initCredentials to support different security 82 // configurations between source and destination clusters, so we'll use that as an example 83 String prefix = "hbase.mapred.output."; 84 conf.set("hbase.security.authentication", "kerberos"); 85 conf.set("hbase.regionserver.kerberos.principal", "hbasesource"); 86 HBaseConfiguration.setWithPrefix(conf, prefix, 87 ImmutableMap.of( 88 "hbase.regionserver.kerberos.principal", "hbasedest", 89 "", "shouldbemissing") 90 .entrySet()); 91 92 Configuration subsetConf = HBaseConfiguration.subset(conf, prefix); 93 assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal")); 94 assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal")); 95 assertNull(subsetConf.get("hbase.security.authentication")); 96 assertNull(subsetConf.get("")); 97 98 Configuration mergedConf = HBaseConfiguration.create(conf); 99 HBaseConfiguration.merge(mergedConf, subsetConf); 100 101 assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal")); 102 assertEquals("kerberos", mergedConf.get("hbase.security.authentication")); 103 assertEquals("shouldbemissing", mergedConf.get(prefix)); 104 } 105 106 @Test testGetPassword()107 public void testGetPassword() throws Exception { 108 Configuration conf = HBaseConfiguration.create(); 109 conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file" 110 + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath()); 111 ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient(); 112 if (client.isHadoopCredentialProviderAvailable()) { 113 char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' }; 114 char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' }; 115 client.createEntry(conf, "ssl.keypass.alias", keyPass); 116 client.createEntry(conf, "ssl.storepass.alias", storePass); 117 118 String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null); 119 assertEquals(keypass, new String(keyPass)); 120 121 String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null); 122 assertEquals(storepass, new String(storePass)); 123 } 124 } 125 126 private static class ReflectiveCredentialProviderClient { 127 public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME = 128 "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory"; 129 public static final String 130 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders"; 131 132 public static final String HADOOP_CRED_PROVIDER_CLASS_NAME = 133 "org.apache.hadoop.security.alias.CredentialProvider"; 134 public static final String 135 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME = 136 "getCredentialEntry"; 137 public static final String 138 HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases"; 139 public static final String 140 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME = 141 "createCredentialEntry"; 142 public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush"; 143 144 public static final String HADOOP_CRED_ENTRY_CLASS_NAME = 145 "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry"; 146 public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME = 147 "getCredential"; 148 149 public static final String CREDENTIAL_PROVIDER_PATH = 150 "hadoop.security.credential.provider.path"; 151 152 private static Object hadoopCredProviderFactory = null; 153 private static Method getProvidersMethod = null; 154 private static Method getAliasesMethod = null; 155 private static Method getCredentialEntryMethod = null; 156 private static Method getCredentialMethod = null; 157 private static Method createCredentialEntryMethod = null; 158 private static Method flushMethod = null; 159 private static Boolean hadoopClassesAvailable = null; 160 161 /** 162 * Determine if we can load the necessary CredentialProvider classes. Only 163 * loaded the first time, so subsequent invocations of this method should 164 * return fast. 165 * 166 * @return True if the CredentialProvider classes/methods are available, 167 * false otherwise. 168 */ isHadoopCredentialProviderAvailable()169 private boolean isHadoopCredentialProviderAvailable() { 170 if (null != hadoopClassesAvailable) { 171 // Make sure everything is initialized as expected 172 if (hadoopClassesAvailable && null != getProvidersMethod 173 && null != hadoopCredProviderFactory 174 && null != getCredentialEntryMethod && null != getCredentialMethod) { 175 return true; 176 } else { 177 // Otherwise we failed to load it 178 return false; 179 } 180 } 181 182 hadoopClassesAvailable = false; 183 184 // Load Hadoop CredentialProviderFactory 185 Class<?> hadoopCredProviderFactoryClz = null; 186 try { 187 hadoopCredProviderFactoryClz = Class 188 .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME); 189 } catch (ClassNotFoundException e) { 190 return false; 191 } 192 // Instantiate Hadoop CredentialProviderFactory 193 try { 194 hadoopCredProviderFactory = hadoopCredProviderFactoryClz.newInstance(); 195 } catch (InstantiationException e) { 196 return false; 197 } catch (IllegalAccessException e) { 198 return false; 199 } 200 201 try { 202 getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz, 203 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME, 204 Configuration.class); 205 // Load Hadoop CredentialProvider 206 Class<?> hadoopCredProviderClz = null; 207 hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME); 208 getCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 209 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class); 210 211 getAliasesMethod = loadMethod(hadoopCredProviderClz, 212 HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME); 213 214 createCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 215 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME, 216 String.class, char[].class); 217 218 flushMethod = loadMethod(hadoopCredProviderClz, 219 HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME); 220 221 // Load Hadoop CredentialEntry 222 Class<?> hadoopCredentialEntryClz = null; 223 try { 224 hadoopCredentialEntryClz = Class 225 .forName(HADOOP_CRED_ENTRY_CLASS_NAME); 226 } catch (ClassNotFoundException e) { 227 LOG.error("Failed to load class:" + e); 228 return false; 229 } 230 231 getCredentialMethod = loadMethod(hadoopCredentialEntryClz, 232 HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME); 233 } catch (Exception e1) { 234 return false; 235 } 236 237 hadoopClassesAvailable = true; 238 LOG.info("Credential provider classes have been" + 239 " loaded and initialized successfully through reflection."); 240 return true; 241 242 } 243 loadMethod(Class<?> clz, String name, Class<?>... classes)244 private Method loadMethod(Class<?> clz, String name, Class<?>... classes) 245 throws Exception { 246 Method method = null; 247 try { 248 method = clz.getMethod(name, classes); 249 } catch (SecurityException e) { 250 fail("security exception caught for: " + name + " in " + 251 clz.getCanonicalName()); 252 throw e; 253 } catch (NoSuchMethodException e) { 254 LOG.error("Failed to load the " + name + ": " + e); 255 fail("no such method: " + name + " in " + clz.getCanonicalName()); 256 throw e; 257 } 258 return method; 259 } 260 261 /** 262 * Wrapper to fetch the configured {@code List<CredentialProvider>}s. 263 * 264 * @param conf 265 * Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined 266 * @return List of CredentialProviders, or null if they could not be loaded 267 */ 268 @SuppressWarnings("unchecked") getCredentialProviders(Configuration conf)269 protected List<Object> getCredentialProviders(Configuration conf) { 270 // Call CredentialProviderFactory.getProviders(Configuration) 271 Object providersObj = null; 272 try { 273 providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory, 274 conf); 275 } catch (IllegalArgumentException e) { 276 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 277 ": " + e); 278 return null; 279 } catch (IllegalAccessException e) { 280 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 281 ": " + e); 282 return null; 283 } catch (InvocationTargetException e) { 284 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 285 ": " + e); 286 return null; 287 } 288 289 // Cast the Object to List<Object> (actually List<CredentialProvider>) 290 try { 291 return (List<Object>) providersObj; 292 } catch (ClassCastException e) { 293 return null; 294 } 295 } 296 297 /** 298 * Create a CredentialEntry using the configured Providers. 299 * If multiple CredentialProviders are configured, the first will be used. 300 * 301 * @param conf 302 * Configuration for the CredentialProvider 303 * @param name 304 * CredentialEntry name (alias) 305 * @param credential 306 * The credential 307 */ createEntry(Configuration conf, String name, char[] credential)308 public void createEntry(Configuration conf, String name, char[] credential) 309 throws Exception { 310 311 if (!isHadoopCredentialProviderAvailable()) { 312 return; 313 } 314 315 List<Object> providers = getCredentialProviders(conf); 316 if (null == providers) { 317 throw new IOException("Could not fetch any CredentialProviders, " + 318 "is the implementation available?"); 319 } 320 321 Object provider = providers.get(0); 322 createEntryInProvider(provider, name, credential); 323 } 324 325 /** 326 * Create a CredentialEntry with the give name and credential in the 327 * credentialProvider. The credentialProvider argument must be an instance 328 * of Hadoop 329 * CredentialProvider. 330 * 331 * @param credentialProvider 332 * Instance of CredentialProvider 333 * @param name 334 * CredentialEntry name (alias) 335 * @param credential 336 * The credential to store 337 */ createEntryInProvider(Object credentialProvider, String name, char[] credential)338 private void createEntryInProvider(Object credentialProvider, 339 String name, char[] credential) throws Exception { 340 341 if (!isHadoopCredentialProviderAvailable()) { 342 return; 343 } 344 345 try { 346 createCredentialEntryMethod.invoke(credentialProvider, name, credential); 347 } catch (IllegalArgumentException e) { 348 return; 349 } catch (IllegalAccessException e) { 350 return; 351 } catch (InvocationTargetException e) { 352 return; 353 } 354 355 try { 356 flushMethod.invoke(credentialProvider); 357 } catch (IllegalArgumentException e) { 358 throw e; 359 } catch (IllegalAccessException e) { 360 throw e; 361 } catch (InvocationTargetException e) { 362 throw e; 363 } 364 } 365 } 366 } 367