001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNull;
022import static org.junit.Assert.fail;
023
024import java.io.File;
025import java.io.IOException;
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Method;
028import java.util.List;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.testclassification.MiscTests;
031import org.apache.hadoop.hbase.testclassification.SmallTests;
032import org.junit.AfterClass;
033import org.junit.ClassRule;
034import org.junit.Test;
035import org.junit.experimental.categories.Category;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
040
041@Category({MiscTests.class, SmallTests.class})
042public class TestHBaseConfiguration {
043
044  @ClassRule
045  public static final HBaseClassTestRule CLASS_RULE =
046      HBaseClassTestRule.forClass(TestHBaseConfiguration.class);
047
048  private static final Logger LOG = LoggerFactory.getLogger(TestHBaseConfiguration.class);
049
050  private static HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility();
051
052  @AfterClass
053  public static void tearDown() throws IOException {
054    UTIL.cleanupTestDir();
055  }
056
057  @Test
058  public void testSubset() throws Exception {
059    Configuration conf = HBaseConfiguration.create();
060    // subset is used in TableMapReduceUtil#initCredentials to support different security
061    // configurations between source and destination clusters, so we'll use that as an example
062    String prefix = "hbase.mapred.output.";
063    conf.set("hbase.security.authentication", "kerberos");
064    conf.set("hbase.regionserver.kerberos.principal", "hbasesource");
065    HBaseConfiguration.setWithPrefix(conf, prefix,
066        ImmutableMap.of(
067            "hbase.regionserver.kerberos.principal", "hbasedest",
068            "", "shouldbemissing")
069            .entrySet());
070
071    Configuration subsetConf = HBaseConfiguration.subset(conf, prefix);
072    assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal"));
073    assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal"));
074    assertNull(subsetConf.get("hbase.security.authentication"));
075    assertNull(subsetConf.get(""));
076
077    Configuration mergedConf = HBaseConfiguration.create(conf);
078    HBaseConfiguration.merge(mergedConf, subsetConf);
079
080    assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal"));
081    assertEquals("kerberos", mergedConf.get("hbase.security.authentication"));
082    assertEquals("shouldbemissing", mergedConf.get(prefix));
083  }
084
085  @Test
086  public void testGetPassword() throws Exception {
087    Configuration conf = HBaseConfiguration.create();
088    conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file"
089        + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath());
090    ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient();
091    if (client.isHadoopCredentialProviderAvailable()) {
092      char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' };
093      char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' };
094      client.createEntry(conf, "ssl.keypass.alias", keyPass);
095      client.createEntry(conf, "ssl.storepass.alias", storePass);
096
097      String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null);
098      assertEquals(keypass, new String(keyPass));
099
100      String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null);
101      assertEquals(storepass, new String(storePass));
102    }
103  }
104
105  private static class ReflectiveCredentialProviderClient {
106    public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME =
107        "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory";
108    public static final String
109      HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders";
110
111    public static final String HADOOP_CRED_PROVIDER_CLASS_NAME =
112        "org.apache.hadoop.security.alias.CredentialProvider";
113    public static final String
114        HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME =
115        "getCredentialEntry";
116    public static final String
117        HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases";
118    public static final String
119        HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME =
120        "createCredentialEntry";
121    public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush";
122
123    public static final String HADOOP_CRED_ENTRY_CLASS_NAME =
124        "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry";
125    public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME =
126        "getCredential";
127
128    public static final String CREDENTIAL_PROVIDER_PATH =
129        "hadoop.security.credential.provider.path";
130
131    private static Object hadoopCredProviderFactory = null;
132    private static Method getProvidersMethod = null;
133    private static Method getAliasesMethod = null;
134    private static Method getCredentialEntryMethod = null;
135    private static Method getCredentialMethod = null;
136    private static Method createCredentialEntryMethod = null;
137    private static Method flushMethod = null;
138    private static Boolean hadoopClassesAvailable = null;
139
140    /**
141     * Determine if we can load the necessary CredentialProvider classes. Only
142     * loaded the first time, so subsequent invocations of this method should
143     * return fast.
144     *
145     * @return True if the CredentialProvider classes/methods are available,
146     *         false otherwise.
147     */
148    private boolean isHadoopCredentialProviderAvailable() {
149      if (null != hadoopClassesAvailable) {
150        // Make sure everything is initialized as expected
151        if (hadoopClassesAvailable && null != getProvidersMethod
152            && null != hadoopCredProviderFactory
153            && null != getCredentialEntryMethod && null != getCredentialMethod) {
154          return true;
155        } else {
156          // Otherwise we failed to load it
157          return false;
158        }
159      }
160
161      hadoopClassesAvailable = false;
162
163      // Load Hadoop CredentialProviderFactory
164      Class<?> hadoopCredProviderFactoryClz = null;
165      try {
166        hadoopCredProviderFactoryClz = Class
167            .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME);
168      } catch (ClassNotFoundException e) {
169        return false;
170      }
171      // Instantiate Hadoop CredentialProviderFactory
172      try {
173        hadoopCredProviderFactory =
174          hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance();
175      } catch (Exception e) {
176        return false;
177      }
178
179      try {
180        getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz,
181            HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME,
182            Configuration.class);
183        // Load Hadoop CredentialProvider
184        Class<?> hadoopCredProviderClz = null;
185        hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME);
186        getCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
187            HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class);
188
189        getAliasesMethod = loadMethod(hadoopCredProviderClz,
190            HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME);
191
192        createCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
193            HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME,
194            String.class, char[].class);
195
196        flushMethod = loadMethod(hadoopCredProviderClz,
197            HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME);
198
199        // Load Hadoop CredentialEntry
200        Class<?> hadoopCredentialEntryClz = null;
201        try {
202          hadoopCredentialEntryClz = Class
203              .forName(HADOOP_CRED_ENTRY_CLASS_NAME);
204        } catch (ClassNotFoundException e) {
205          LOG.error("Failed to load class:" + e);
206          return false;
207        }
208
209        getCredentialMethod = loadMethod(hadoopCredentialEntryClz,
210            HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME);
211      } catch (Exception e1) {
212        return false;
213      }
214
215      hadoopClassesAvailable = true;
216      LOG.info("Credential provider classes have been" +
217          " loaded and initialized successfully through reflection.");
218      return true;
219
220    }
221
222    private Method loadMethod(Class<?> clz, String name, Class<?>... classes)
223        throws Exception {
224      Method method = null;
225      try {
226        method = clz.getMethod(name, classes);
227      } catch (SecurityException e) {
228        fail("security exception caught for: " + name + " in " +
229      clz.getCanonicalName());
230        throw e;
231      } catch (NoSuchMethodException e) {
232        LOG.error("Failed to load the " + name + ": " + e);
233        fail("no such method: " + name + " in " + clz.getCanonicalName());
234        throw e;
235      }
236      return method;
237    }
238
239    /**
240     * Wrapper to fetch the configured {@code List<CredentialProvider>}s.
241     *
242     * @param conf
243     *    Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined
244     * @return List of CredentialProviders, or null if they could not be loaded
245     */
246    @SuppressWarnings("unchecked")
247    protected  List<Object> getCredentialProviders(Configuration conf) {
248      // Call CredentialProviderFactory.getProviders(Configuration)
249      Object providersObj = null;
250      try {
251        providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory,
252            conf);
253      } catch (IllegalArgumentException e) {
254        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
255            ": " + e);
256        return null;
257      } catch (IllegalAccessException e) {
258        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
259            ": " + e);
260        return null;
261      } catch (InvocationTargetException e) {
262        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
263            ": " + e);
264        return null;
265      }
266
267      // Cast the Object to List<Object> (actually List<CredentialProvider>)
268      try {
269        return (List<Object>) providersObj;
270      } catch (ClassCastException e) {
271        return null;
272      }
273    }
274
275    /**
276     * Create a CredentialEntry using the configured Providers.
277     * If multiple CredentialProviders are configured, the first will be used.
278     *
279     * @param conf
280     *          Configuration for the CredentialProvider
281     * @param name
282     *          CredentialEntry name (alias)
283     * @param credential
284     *          The credential
285     */
286    public  void createEntry(Configuration conf, String name, char[] credential)
287        throws Exception {
288
289      if (!isHadoopCredentialProviderAvailable()) {
290        return;
291      }
292
293      List<Object> providers = getCredentialProviders(conf);
294      if (null == providers) {
295        throw new IOException("Could not fetch any CredentialProviders, " +
296            "is the implementation available?");
297      }
298
299      Object provider = providers.get(0);
300      createEntryInProvider(provider, name, credential);
301    }
302
303    /**
304     * Create a CredentialEntry with the give name and credential in the
305     * credentialProvider. The credentialProvider argument must be an instance
306     * of Hadoop
307     * CredentialProvider.
308     *
309     * @param credentialProvider
310     *          Instance of CredentialProvider
311     * @param name
312     *          CredentialEntry name (alias)
313     * @param credential
314     *          The credential to store
315     */
316    private void createEntryInProvider(Object credentialProvider,
317        String name, char[] credential) throws Exception {
318
319      if (!isHadoopCredentialProviderAvailable()) {
320        return;
321      }
322
323      try {
324        createCredentialEntryMethod.invoke(credentialProvider, name, credential);
325      } catch (IllegalArgumentException e) {
326        return;
327      } catch (IllegalAccessException e) {
328        return;
329      } catch (InvocationTargetException e) {
330        return;
331      }
332
333      try {
334        flushMethod.invoke(credentialProvider);
335      } catch (IllegalArgumentException e) {
336        throw e;
337      } catch (IllegalAccessException e) {
338        throw e;
339      } catch (InvocationTargetException e) {
340        throw e;
341      }
342    }
343  }
344}