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