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