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