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 044 @ClassRule 045 public static final HBaseClassTestRule CLASS_RULE = 046 HBaseClassTestRule.forClass(TestHBaseConfiguration.class); 047 048 private static final Logger LOG = LoggerFactory.getLogger(TestHBaseConfiguration.class); 049 050 private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility(); 051 052 @AfterClass 053 public static void tearDown() throws IOException { 054 UTIL.cleanupTestDir(); 055 } 056 057 @Test 058 public void testGetIntDeprecated() { 059 int VAL = 1, VAL2 = 2; 060 String NAME = "foo"; 061 String DEPRECATED_NAME = "foo.deprecated"; 062 063 Configuration conf = HBaseConfiguration.create(); 064 conf.setInt(NAME, VAL); 065 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 066 067 conf = HBaseConfiguration.create(); 068 conf.setInt(DEPRECATED_NAME, VAL); 069 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 070 071 conf = HBaseConfiguration.create(); 072 conf.setInt(DEPRECATED_NAME, VAL); 073 conf.setInt(NAME, VAL); 074 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 075 076 conf = HBaseConfiguration.create(); 077 conf.setInt(DEPRECATED_NAME, VAL); 078 conf.setInt(NAME, VAL2); // deprecated value will override this 079 assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0)); 080 } 081 082 @Test 083 public void testSubset() throws Exception { 084 Configuration conf = HBaseConfiguration.create(); 085 // subset is used in TableMapReduceUtil#initCredentials to support different security 086 // configurations between source and destination clusters, so we'll use that as an example 087 String prefix = "hbase.mapred.output."; 088 conf.set("hbase.security.authentication", "kerberos"); 089 conf.set("hbase.regionserver.kerberos.principal", "hbasesource"); 090 HBaseConfiguration.setWithPrefix(conf, prefix, 091 ImmutableMap.of( 092 "hbase.regionserver.kerberos.principal", "hbasedest", 093 "", "shouldbemissing") 094 .entrySet()); 095 096 Configuration subsetConf = HBaseConfiguration.subset(conf, prefix); 097 assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal")); 098 assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal")); 099 assertNull(subsetConf.get("hbase.security.authentication")); 100 assertNull(subsetConf.get("")); 101 102 Configuration mergedConf = HBaseConfiguration.create(conf); 103 HBaseConfiguration.merge(mergedConf, subsetConf); 104 105 assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal")); 106 assertEquals("kerberos", mergedConf.get("hbase.security.authentication")); 107 assertEquals("shouldbemissing", mergedConf.get(prefix)); 108 } 109 110 @Test 111 public void testGetPassword() throws Exception { 112 Configuration conf = HBaseConfiguration.create(); 113 conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file" 114 + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath()); 115 ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient(); 116 if (client.isHadoopCredentialProviderAvailable()) { 117 char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' }; 118 char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' }; 119 client.createEntry(conf, "ssl.keypass.alias", keyPass); 120 client.createEntry(conf, "ssl.storepass.alias", storePass); 121 122 String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null); 123 assertEquals(keypass, new String(keyPass)); 124 125 String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null); 126 assertEquals(storepass, new String(storePass)); 127 } 128 } 129 130 private static class ReflectiveCredentialProviderClient { 131 public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME = 132 "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory"; 133 public static final String 134 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders"; 135 136 public static final String HADOOP_CRED_PROVIDER_CLASS_NAME = 137 "org.apache.hadoop.security.alias.CredentialProvider"; 138 public static final String 139 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME = 140 "getCredentialEntry"; 141 public static final String 142 HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases"; 143 public static final String 144 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME = 145 "createCredentialEntry"; 146 public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush"; 147 148 public static final String HADOOP_CRED_ENTRY_CLASS_NAME = 149 "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry"; 150 public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME = 151 "getCredential"; 152 153 public static final String CREDENTIAL_PROVIDER_PATH = 154 "hadoop.security.credential.provider.path"; 155 156 private static Object hadoopCredProviderFactory = null; 157 private static Method getProvidersMethod = null; 158 private static Method getAliasesMethod = null; 159 private static Method getCredentialEntryMethod = null; 160 private static Method getCredentialMethod = null; 161 private static Method createCredentialEntryMethod = null; 162 private static Method flushMethod = null; 163 private static Boolean hadoopClassesAvailable = null; 164 165 /** 166 * Determine if we can load the necessary CredentialProvider classes. Only 167 * loaded the first time, so subsequent invocations of this method should 168 * return fast. 169 * 170 * @return True if the CredentialProvider classes/methods are available, 171 * false otherwise. 172 */ 173 private boolean isHadoopCredentialProviderAvailable() { 174 if (null != hadoopClassesAvailable) { 175 // Make sure everything is initialized as expected 176 if (hadoopClassesAvailable && null != getProvidersMethod 177 && null != hadoopCredProviderFactory 178 && null != getCredentialEntryMethod && null != getCredentialMethod) { 179 return true; 180 } else { 181 // Otherwise we failed to load it 182 return false; 183 } 184 } 185 186 hadoopClassesAvailable = false; 187 188 // Load Hadoop CredentialProviderFactory 189 Class<?> hadoopCredProviderFactoryClz = null; 190 try { 191 hadoopCredProviderFactoryClz = Class 192 .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME); 193 } catch (ClassNotFoundException e) { 194 return false; 195 } 196 // Instantiate Hadoop CredentialProviderFactory 197 try { 198 hadoopCredProviderFactory = 199 hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance(); 200 } catch (Exception e) { 201 return false; 202 } 203 204 try { 205 getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz, 206 HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME, 207 Configuration.class); 208 // Load Hadoop CredentialProvider 209 Class<?> hadoopCredProviderClz = null; 210 hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME); 211 getCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 212 HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class); 213 214 getAliasesMethod = loadMethod(hadoopCredProviderClz, 215 HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME); 216 217 createCredentialEntryMethod = loadMethod(hadoopCredProviderClz, 218 HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME, 219 String.class, char[].class); 220 221 flushMethod = loadMethod(hadoopCredProviderClz, 222 HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME); 223 224 // Load Hadoop CredentialEntry 225 Class<?> hadoopCredentialEntryClz = null; 226 try { 227 hadoopCredentialEntryClz = Class 228 .forName(HADOOP_CRED_ENTRY_CLASS_NAME); 229 } catch (ClassNotFoundException e) { 230 LOG.error("Failed to load class:" + e); 231 return false; 232 } 233 234 getCredentialMethod = loadMethod(hadoopCredentialEntryClz, 235 HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME); 236 } catch (Exception e1) { 237 return false; 238 } 239 240 hadoopClassesAvailable = true; 241 LOG.info("Credential provider classes have been" + 242 " loaded and initialized successfully through reflection."); 243 return true; 244 245 } 246 247 private Method loadMethod(Class<?> clz, String name, Class<?>... classes) 248 throws Exception { 249 Method method = null; 250 try { 251 method = clz.getMethod(name, classes); 252 } catch (SecurityException e) { 253 fail("security exception caught for: " + name + " in " + 254 clz.getCanonicalName()); 255 throw e; 256 } catch (NoSuchMethodException e) { 257 LOG.error("Failed to load the " + name + ": " + e); 258 fail("no such method: " + name + " in " + clz.getCanonicalName()); 259 throw e; 260 } 261 return method; 262 } 263 264 /** 265 * Wrapper to fetch the configured {@code List<CredentialProvider>}s. 266 * 267 * @param conf 268 * Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined 269 * @return List of CredentialProviders, or null if they could not be loaded 270 */ 271 @SuppressWarnings("unchecked") 272 protected List<Object> getCredentialProviders(Configuration conf) { 273 // Call CredentialProviderFactory.getProviders(Configuration) 274 Object providersObj = null; 275 try { 276 providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory, 277 conf); 278 } catch (IllegalArgumentException e) { 279 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 280 ": " + e); 281 return null; 282 } catch (IllegalAccessException e) { 283 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 284 ": " + e); 285 return null; 286 } catch (InvocationTargetException e) { 287 LOG.error("Failed to invoke: " + getProvidersMethod.getName() + 288 ": " + e); 289 return null; 290 } 291 292 // Cast the Object to List<Object> (actually List<CredentialProvider>) 293 try { 294 return (List<Object>) providersObj; 295 } catch (ClassCastException e) { 296 return null; 297 } 298 } 299 300 /** 301 * Create a CredentialEntry using the configured Providers. 302 * If multiple CredentialProviders are configured, the first will be used. 303 * 304 * @param conf 305 * Configuration for the CredentialProvider 306 * @param name 307 * CredentialEntry name (alias) 308 * @param credential 309 * The credential 310 */ 311 public void createEntry(Configuration conf, String name, char[] credential) 312 throws Exception { 313 314 if (!isHadoopCredentialProviderAvailable()) { 315 return; 316 } 317 318 List<Object> providers = getCredentialProviders(conf); 319 if (null == providers) { 320 throw new IOException("Could not fetch any CredentialProviders, " + 321 "is the implementation available?"); 322 } 323 324 Object provider = providers.get(0); 325 createEntryInProvider(provider, name, credential); 326 } 327 328 /** 329 * Create a CredentialEntry with the give name and credential in the 330 * credentialProvider. The credentialProvider argument must be an instance 331 * of Hadoop 332 * CredentialProvider. 333 * 334 * @param credentialProvider 335 * Instance of CredentialProvider 336 * @param name 337 * CredentialEntry name (alias) 338 * @param credential 339 * The credential to store 340 */ 341 private void createEntryInProvider(Object credentialProvider, 342 String name, char[] credential) throws Exception { 343 344 if (!isHadoopCredentialProviderAvailable()) { 345 return; 346 } 347 348 try { 349 createCredentialEntryMethod.invoke(credentialProvider, name, credential); 350 } catch (IllegalArgumentException e) { 351 return; 352 } catch (IllegalAccessException e) { 353 return; 354 } catch (InvocationTargetException e) { 355 return; 356 } 357 358 try { 359 flushMethod.invoke(credentialProvider); 360 } catch (IllegalArgumentException e) { 361 throw e; 362 } catch (IllegalAccessException e) { 363 throw e; 364 } catch (InvocationTargetException e) { 365 throw e; 366 } 367 } 368 } 369}