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 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.junit.AfterClass;
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    private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility();
46  
47    @AfterClass
48    public static void tearDown() throws IOException {
49      UTIL.cleanupTestDir();
50    }
51  
52    @Test
53    public void testGetIntDeprecated() {
54      int VAL = 1, VAL2 = 2;
55      String NAME = "foo";
56      String DEPRECATED_NAME = "foo.deprecated";
57  
58      Configuration conf = HBaseConfiguration.create();
59      conf.setInt(NAME, VAL);
60      assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
61  
62      conf = HBaseConfiguration.create();
63      conf.setInt(DEPRECATED_NAME, VAL);
64      assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
65  
66      conf = HBaseConfiguration.create();
67      conf.setInt(DEPRECATED_NAME, VAL);
68      conf.setInt(NAME, VAL);
69      assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
70  
71      conf = HBaseConfiguration.create();
72      conf.setInt(DEPRECATED_NAME, VAL);
73      conf.setInt(NAME, VAL2); // deprecated value will override this
74      assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
75    }
76  
77    @Test
78    public void testSubset() throws Exception {
79      Configuration conf = HBaseConfiguration.create();
80      // subset is used in TableMapReduceUtil#initCredentials to support different security
81      // configurations between source and destination clusters, so we'll use that as an example
82      String prefix = "hbase.mapred.output.";
83      conf.set("hbase.security.authentication", "kerberos");
84      conf.set("hbase.regionserver.kerberos.principal", "hbasesource");
85      HBaseConfiguration.setWithPrefix(conf, prefix,
86          ImmutableMap.of(
87              "hbase.regionserver.kerberos.principal", "hbasedest",
88              "", "shouldbemissing")
89              .entrySet());
90  
91      Configuration subsetConf = HBaseConfiguration.subset(conf, prefix);
92      assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal"));
93      assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal"));
94      assertNull(subsetConf.get("hbase.security.authentication"));
95      assertNull(subsetConf.get(""));
96  
97      Configuration mergedConf = HBaseConfiguration.create(conf);
98      HBaseConfiguration.merge(mergedConf, subsetConf);
99  
100     assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal"));
101     assertEquals("kerberos", mergedConf.get("hbase.security.authentication"));
102     assertEquals("shouldbemissing", mergedConf.get(prefix));
103   }
104 
105   @Test
106   public void testGetPassword() throws Exception {
107     Configuration conf = HBaseConfiguration.create();
108     conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file"
109         + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath());
110     ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient();
111     if (client.isHadoopCredentialProviderAvailable()) {
112       char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' };
113       char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' };
114       client.createEntry(conf, "ssl.keypass.alias", keyPass);
115       client.createEntry(conf, "ssl.storepass.alias", storePass);
116 
117       String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null);
118       assertEquals(keypass, new String(keyPass));
119 
120       String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null);
121       assertEquals(storepass, new String(storePass));
122     }
123   }
124 
125   private static class ReflectiveCredentialProviderClient {
126     public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME =
127         "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory";
128     public static final String
129       HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders";
130 
131     public static final String HADOOP_CRED_PROVIDER_CLASS_NAME =
132         "org.apache.hadoop.security.alias.CredentialProvider";
133     public static final String
134         HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME =
135         "getCredentialEntry";
136     public static final String
137         HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases";
138     public static final String
139         HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME =
140         "createCredentialEntry";
141     public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush";
142 
143     public static final String HADOOP_CRED_ENTRY_CLASS_NAME =
144         "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry";
145     public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME =
146         "getCredential";
147 
148     public static final String CREDENTIAL_PROVIDER_PATH =
149         "hadoop.security.credential.provider.path";
150 
151     private static Object hadoopCredProviderFactory = null;
152     private static Method getProvidersMethod = null;
153     private static Method getAliasesMethod = null;
154     private static Method getCredentialEntryMethod = null;
155     private static Method getCredentialMethod = null;
156     private static Method createCredentialEntryMethod = null;
157     private static Method flushMethod = null;
158     private static Boolean hadoopClassesAvailable = null;
159 
160     /**
161      * Determine if we can load the necessary CredentialProvider classes. Only
162      * loaded the first time, so subsequent invocations of this method should
163      * return fast.
164      *
165      * @return True if the CredentialProvider classes/methods are available,
166      *         false otherwise.
167      */
168     private boolean isHadoopCredentialProviderAvailable() {
169       if (null != hadoopClassesAvailable) {
170         // Make sure everything is initialized as expected
171         if (hadoopClassesAvailable && null != getProvidersMethod
172             && null != hadoopCredProviderFactory
173             && null != getCredentialEntryMethod && null != getCredentialMethod) {
174           return true;
175         } else {
176           // Otherwise we failed to load it
177           return false;
178         }
179       }
180 
181       hadoopClassesAvailable = false;
182 
183       // Load Hadoop CredentialProviderFactory
184       Class<?> hadoopCredProviderFactoryClz = null;
185       try {
186         hadoopCredProviderFactoryClz = Class
187             .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME);
188       } catch (ClassNotFoundException e) {
189         return false;
190       }
191       // Instantiate Hadoop CredentialProviderFactory
192       try {
193         hadoopCredProviderFactory = hadoopCredProviderFactoryClz.newInstance();
194       } catch (InstantiationException e) {
195         return false;
196       } catch (IllegalAccessException 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 }