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 =
195           hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance();
196       } catch (Exception e) {
197         return false;
198       }
199 
200       try {
201         getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz,
202             HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME,
203             Configuration.class);
204         // Load Hadoop CredentialProvider
205         Class<?> hadoopCredProviderClz = null;
206         hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME);
207         getCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
208             HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class);
209 
210         getAliasesMethod = loadMethod(hadoopCredProviderClz,
211             HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME);
212 
213         createCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
214             HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME,
215             String.class, char[].class);
216 
217         flushMethod = loadMethod(hadoopCredProviderClz,
218             HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME);
219 
220         // Load Hadoop CredentialEntry
221         Class<?> hadoopCredentialEntryClz = null;
222         try {
223           hadoopCredentialEntryClz = Class
224               .forName(HADOOP_CRED_ENTRY_CLASS_NAME);
225         } catch (ClassNotFoundException e) {
226           LOG.error("Failed to load class:" + e);
227           return false;
228         }
229 
230         getCredentialMethod = loadMethod(hadoopCredentialEntryClz,
231             HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME);
232       } catch (Exception e1) {
233         return false;
234       }
235 
236       hadoopClassesAvailable = true;
237       LOG.info("Credential provider classes have been" +
238           " loaded and initialized successfully through reflection.");
239       return true;
240 
241     }
242 
243     private Method loadMethod(Class<?> clz, String name, Class<?>... classes)
244         throws Exception {
245       Method method = null;
246       try {
247         method = clz.getMethod(name, classes);
248       } catch (SecurityException e) {
249         fail("security exception caught for: " + name + " in " +
250       clz.getCanonicalName());
251         throw e;
252       } catch (NoSuchMethodException e) {
253         LOG.error("Failed to load the " + name + ": " + e);
254         fail("no such method: " + name + " in " + clz.getCanonicalName());
255         throw e;
256       }
257       return method;
258     }
259 
260     /**
261      * Wrapper to fetch the configured {@code List<CredentialProvider>}s.
262      *
263      * @param conf
264      *    Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined
265      * @return List of CredentialProviders, or null if they could not be loaded
266      */
267     @SuppressWarnings("unchecked")
268     protected  List<Object> getCredentialProviders(Configuration conf) {
269       // Call CredentialProviderFactory.getProviders(Configuration)
270       Object providersObj = null;
271       try {
272         providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory,
273             conf);
274       } catch (IllegalArgumentException e) {
275         LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
276             ": " + e);
277         return null;
278       } catch (IllegalAccessException e) {
279         LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
280             ": " + e);
281         return null;
282       } catch (InvocationTargetException e) {
283         LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
284             ": " + e);
285         return null;
286       }
287 
288       // Cast the Object to List<Object> (actually List<CredentialProvider>)
289       try {
290         return (List<Object>) providersObj;
291       } catch (ClassCastException e) {
292         return null;
293       }
294     }
295 
296     /**
297      * Create a CredentialEntry using the configured Providers.
298      * If multiple CredentialProviders are configured, the first will be used.
299      *
300      * @param conf
301      *          Configuration for the CredentialProvider
302      * @param name
303      *          CredentialEntry name (alias)
304      * @param credential
305      *          The credential
306      */
307     public  void createEntry(Configuration conf, String name, char[] credential)
308         throws Exception {
309 
310       if (!isHadoopCredentialProviderAvailable()) {
311         return;
312       }
313 
314       List<Object> providers = getCredentialProviders(conf);
315       if (null == providers) {
316         throw new IOException("Could not fetch any CredentialProviders, " +
317             "is the implementation available?");
318       }
319 
320       Object provider = providers.get(0);
321       createEntryInProvider(provider, name, credential);
322     }
323 
324     /**
325      * Create a CredentialEntry with the give name and credential in the
326      * credentialProvider. The credentialProvider argument must be an instance
327      * of Hadoop
328      * CredentialProvider.
329      *
330      * @param credentialProvider
331      *          Instance of CredentialProvider
332      * @param name
333      *          CredentialEntry name (alias)
334      * @param credential
335      *          The credential to store
336      */
337     private void createEntryInProvider(Object credentialProvider,
338         String name, char[] credential) throws Exception {
339 
340       if (!isHadoopCredentialProviderAvailable()) {
341         return;
342       }
343 
344       try {
345         createCredentialEntryMethod.invoke(credentialProvider, name, credential);
346       } catch (IllegalArgumentException e) {
347         return;
348       } catch (IllegalAccessException e) {
349         return;
350       } catch (InvocationTargetException e) {
351         return;
352       }
353 
354       try {
355         flushMethod.invoke(credentialProvider);
356       } catch (IllegalArgumentException e) {
357         throw e;
358       } catch (IllegalAccessException e) {
359         throw e;
360       } catch (InvocationTargetException e) {
361         throw e;
362       }
363     }
364   }
365 }