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