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