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