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