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