View Javadoc

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