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 testGetIntDeprecated() {
059    int VAL = 1, VAL2 = 2;
060    String NAME = "foo";
061    String DEPRECATED_NAME = "foo.deprecated";
062
063    Configuration conf = HBaseConfiguration.create();
064    conf.setInt(NAME, VAL);
065    assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
066
067    conf = HBaseConfiguration.create();
068    conf.setInt(DEPRECATED_NAME, VAL);
069    assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
070
071    conf = HBaseConfiguration.create();
072    conf.setInt(DEPRECATED_NAME, VAL);
073    conf.setInt(NAME, VAL);
074    assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
075
076    conf = HBaseConfiguration.create();
077    conf.setInt(DEPRECATED_NAME, VAL);
078    conf.setInt(NAME, VAL2); // deprecated value will override this
079    assertEquals(VAL, HBaseConfiguration.getInt(conf, NAME, DEPRECATED_NAME, 0));
080  }
081
082  @Test
083  public void testSubset() throws Exception {
084    Configuration conf = HBaseConfiguration.create();
085    // subset is used in TableMapReduceUtil#initCredentials to support different security
086    // configurations between source and destination clusters, so we'll use that as an example
087    String prefix = "hbase.mapred.output.";
088    conf.set("hbase.security.authentication", "kerberos");
089    conf.set("hbase.regionserver.kerberos.principal", "hbasesource");
090    HBaseConfiguration.setWithPrefix(conf, prefix,
091        ImmutableMap.of(
092            "hbase.regionserver.kerberos.principal", "hbasedest",
093            "", "shouldbemissing")
094            .entrySet());
095
096    Configuration subsetConf = HBaseConfiguration.subset(conf, prefix);
097    assertNull(subsetConf.get(prefix + "hbase.regionserver.kerberos.principal"));
098    assertEquals("hbasedest", subsetConf.get("hbase.regionserver.kerberos.principal"));
099    assertNull(subsetConf.get("hbase.security.authentication"));
100    assertNull(subsetConf.get(""));
101
102    Configuration mergedConf = HBaseConfiguration.create(conf);
103    HBaseConfiguration.merge(mergedConf, subsetConf);
104
105    assertEquals("hbasedest", mergedConf.get("hbase.regionserver.kerberos.principal"));
106    assertEquals("kerberos", mergedConf.get("hbase.security.authentication"));
107    assertEquals("shouldbemissing", mergedConf.get(prefix));
108  }
109
110  @Test
111  public void testGetPassword() throws Exception {
112    Configuration conf = HBaseConfiguration.create();
113    conf.set(ReflectiveCredentialProviderClient.CREDENTIAL_PROVIDER_PATH, "jceks://file"
114        + new File(UTIL.getDataTestDir().toUri().getPath(), "foo.jks").getCanonicalPath());
115    ReflectiveCredentialProviderClient client = new ReflectiveCredentialProviderClient();
116    if (client.isHadoopCredentialProviderAvailable()) {
117      char[] keyPass = { 'k', 'e', 'y', 'p', 'a', 's', 's' };
118      char[] storePass = { 's', 't', 'o', 'r', 'e', 'p', 'a', 's', 's' };
119      client.createEntry(conf, "ssl.keypass.alias", keyPass);
120      client.createEntry(conf, "ssl.storepass.alias", storePass);
121
122      String keypass = HBaseConfiguration.getPassword(conf, "ssl.keypass.alias", null);
123      assertEquals(keypass, new String(keyPass));
124
125      String storepass = HBaseConfiguration.getPassword(conf, "ssl.storepass.alias", null);
126      assertEquals(storepass, new String(storePass));
127    }
128  }
129
130  private static class ReflectiveCredentialProviderClient {
131    public static final String HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME =
132        "org.apache.hadoop.security.alias.JavaKeyStoreProvider$Factory";
133    public static final String
134      HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME = "getProviders";
135
136    public static final String HADOOP_CRED_PROVIDER_CLASS_NAME =
137        "org.apache.hadoop.security.alias.CredentialProvider";
138    public static final String
139        HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME =
140        "getCredentialEntry";
141    public static final String
142        HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME = "getAliases";
143    public static final String
144        HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME =
145        "createCredentialEntry";
146    public static final String HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME = "flush";
147
148    public static final String HADOOP_CRED_ENTRY_CLASS_NAME =
149        "org.apache.hadoop.security.alias.CredentialProvider$CredentialEntry";
150    public static final String HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME =
151        "getCredential";
152
153    public static final String CREDENTIAL_PROVIDER_PATH =
154        "hadoop.security.credential.provider.path";
155
156    private static Object hadoopCredProviderFactory = null;
157    private static Method getProvidersMethod = null;
158    private static Method getAliasesMethod = null;
159    private static Method getCredentialEntryMethod = null;
160    private static Method getCredentialMethod = null;
161    private static Method createCredentialEntryMethod = null;
162    private static Method flushMethod = null;
163    private static Boolean hadoopClassesAvailable = null;
164
165    /**
166     * Determine if we can load the necessary CredentialProvider classes. Only
167     * loaded the first time, so subsequent invocations of this method should
168     * return fast.
169     *
170     * @return True if the CredentialProvider classes/methods are available,
171     *         false otherwise.
172     */
173    private boolean isHadoopCredentialProviderAvailable() {
174      if (null != hadoopClassesAvailable) {
175        // Make sure everything is initialized as expected
176        if (hadoopClassesAvailable && null != getProvidersMethod
177            && null != hadoopCredProviderFactory
178            && null != getCredentialEntryMethod && null != getCredentialMethod) {
179          return true;
180        } else {
181          // Otherwise we failed to load it
182          return false;
183        }
184      }
185
186      hadoopClassesAvailable = false;
187
188      // Load Hadoop CredentialProviderFactory
189      Class<?> hadoopCredProviderFactoryClz = null;
190      try {
191        hadoopCredProviderFactoryClz = Class
192            .forName(HADOOP_CRED_PROVIDER_FACTORY_CLASS_NAME);
193      } catch (ClassNotFoundException e) {
194        return false;
195      }
196      // Instantiate Hadoop CredentialProviderFactory
197      try {
198        hadoopCredProviderFactory =
199          hadoopCredProviderFactoryClz.getDeclaredConstructor().newInstance();
200      } catch (Exception e) {
201        return false;
202      }
203
204      try {
205        getProvidersMethod = loadMethod(hadoopCredProviderFactoryClz,
206            HADOOP_CRED_PROVIDER_FACTORY_GET_PROVIDERS_METHOD_NAME,
207            Configuration.class);
208        // Load Hadoop CredentialProvider
209        Class<?> hadoopCredProviderClz = null;
210        hadoopCredProviderClz = Class.forName(HADOOP_CRED_PROVIDER_CLASS_NAME);
211        getCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
212            HADOOP_CRED_PROVIDER_GET_CREDENTIAL_ENTRY_METHOD_NAME, String.class);
213
214        getAliasesMethod = loadMethod(hadoopCredProviderClz,
215            HADOOP_CRED_PROVIDER_GET_ALIASES_METHOD_NAME);
216
217        createCredentialEntryMethod = loadMethod(hadoopCredProviderClz,
218            HADOOP_CRED_PROVIDER_CREATE_CREDENTIAL_ENTRY_METHOD_NAME,
219            String.class, char[].class);
220
221        flushMethod = loadMethod(hadoopCredProviderClz,
222            HADOOP_CRED_PROVIDER_FLUSH_METHOD_NAME);
223
224        // Load Hadoop CredentialEntry
225        Class<?> hadoopCredentialEntryClz = null;
226        try {
227          hadoopCredentialEntryClz = Class
228              .forName(HADOOP_CRED_ENTRY_CLASS_NAME);
229        } catch (ClassNotFoundException e) {
230          LOG.error("Failed to load class:" + e);
231          return false;
232        }
233
234        getCredentialMethod = loadMethod(hadoopCredentialEntryClz,
235            HADOOP_CRED_ENTRY_GET_CREDENTIAL_METHOD_NAME);
236      } catch (Exception e1) {
237        return false;
238      }
239
240      hadoopClassesAvailable = true;
241      LOG.info("Credential provider classes have been" +
242          " loaded and initialized successfully through reflection.");
243      return true;
244
245    }
246
247    private Method loadMethod(Class<?> clz, String name, Class<?>... classes)
248        throws Exception {
249      Method method = null;
250      try {
251        method = clz.getMethod(name, classes);
252      } catch (SecurityException e) {
253        fail("security exception caught for: " + name + " in " +
254      clz.getCanonicalName());
255        throw e;
256      } catch (NoSuchMethodException e) {
257        LOG.error("Failed to load the " + name + ": " + e);
258        fail("no such method: " + name + " in " + clz.getCanonicalName());
259        throw e;
260      }
261      return method;
262    }
263
264    /**
265     * Wrapper to fetch the configured {@code List<CredentialProvider>}s.
266     *
267     * @param conf
268     *    Configuration with GENERAL_SECURITY_CREDENTIAL_PROVIDER_PATHS defined
269     * @return List of CredentialProviders, or null if they could not be loaded
270     */
271    @SuppressWarnings("unchecked")
272    protected  List<Object> getCredentialProviders(Configuration conf) {
273      // Call CredentialProviderFactory.getProviders(Configuration)
274      Object providersObj = null;
275      try {
276        providersObj = getProvidersMethod.invoke(hadoopCredProviderFactory,
277            conf);
278      } catch (IllegalArgumentException e) {
279        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
280            ": " + e);
281        return null;
282      } catch (IllegalAccessException e) {
283        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
284            ": " + e);
285        return null;
286      } catch (InvocationTargetException e) {
287        LOG.error("Failed to invoke: " + getProvidersMethod.getName() +
288            ": " + e);
289        return null;
290      }
291
292      // Cast the Object to List<Object> (actually List<CredentialProvider>)
293      try {
294        return (List<Object>) providersObj;
295      } catch (ClassCastException e) {
296        return null;
297      }
298    }
299
300    /**
301     * Create a CredentialEntry using the configured Providers.
302     * If multiple CredentialProviders are configured, the first will be used.
303     *
304     * @param conf
305     *          Configuration for the CredentialProvider
306     * @param name
307     *          CredentialEntry name (alias)
308     * @param credential
309     *          The credential
310     */
311    public  void createEntry(Configuration conf, String name, char[] credential)
312        throws Exception {
313
314      if (!isHadoopCredentialProviderAvailable()) {
315        return;
316      }
317
318      List<Object> providers = getCredentialProviders(conf);
319      if (null == providers) {
320        throw new IOException("Could not fetch any CredentialProviders, " +
321            "is the implementation available?");
322      }
323
324      Object provider = providers.get(0);
325      createEntryInProvider(provider, name, credential);
326    }
327
328    /**
329     * Create a CredentialEntry with the give name and credential in the
330     * credentialProvider. The credentialProvider argument must be an instance
331     * of Hadoop
332     * CredentialProvider.
333     *
334     * @param credentialProvider
335     *          Instance of CredentialProvider
336     * @param name
337     *          CredentialEntry name (alias)
338     * @param credential
339     *          The credential to store
340     */
341    private void createEntryInProvider(Object credentialProvider,
342        String name, char[] credential) throws Exception {
343
344      if (!isHadoopCredentialProviderAvailable()) {
345        return;
346      }
347
348      try {
349        createCredentialEntryMethod.invoke(credentialProvider, name, credential);
350      } catch (IllegalArgumentException e) {
351        return;
352      } catch (IllegalAccessException e) {
353        return;
354      } catch (InvocationTargetException e) {
355        return;
356      }
357
358      try {
359        flushMethod.invoke(credentialProvider);
360      } catch (IllegalArgumentException e) {
361        throw e;
362      } catch (IllegalAccessException e) {
363        throw e;
364      } catch (InvocationTargetException e) {
365        throw e;
366      }
367    }
368  }
369}