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.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
49    public static void tearDown() throws IOException {
50      UTIL.cleanupTestDir();
51    }
52  
53    @Test
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
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
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      */
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 
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")
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      */
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      */
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 }