diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java
index 2db865d8cf3..21a4828b49e 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java
@@ -26,6 +26,7 @@ import javax.security.sasl.SaslClient;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.security.SaslUtil;
+import org.apache.hadoop.hbase.security.SecurityConstants;
import org.apache.hadoop.hbase.security.SecurityInfo;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.security.SecurityUtil;
@@ -44,15 +45,40 @@ public class GssSaslClientAuthenticationProvider extends GssSaslAuthenticationPr
private static final Logger LOG = LoggerFactory.getLogger(
GssSaslClientAuthenticationProvider.class);
+ private static boolean useCanonicalHostname(Configuration conf) {
+ return !conf.getBoolean(
+ SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS,
+ SecurityConstants.DEFAULT_UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS);
+ }
+
+ public static String getHostnameForServerPrincipal(Configuration conf, InetAddress addr) {
+ final String hostname;
+
+ if (useCanonicalHostname(conf)) {
+ hostname = addr.getCanonicalHostName();
+ if (hostname.equals(addr.getHostAddress())) {
+ LOG.warn("Canonical hostname for SASL principal is the same with IP address: "
+ + hostname + ", " + addr.getHostName() + ". Check DNS configuration or consider "
+ + SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS
+ + "=true");
+ }
+ } else {
+ hostname = addr.getHostName();
+ }
+
+ return hostname.toLowerCase();
+ }
+
String getServerPrincipal(Configuration conf, SecurityInfo securityInfo, InetAddress server)
throws IOException {
+ String hostname = getHostnameForServerPrincipal(conf, server);
+
String serverKey = securityInfo.getServerPrincipal();
if (serverKey == null) {
throw new IllegalArgumentException(
"Can't obtain server Kerberos config key from SecurityInfo");
}
- return SecurityUtil.getServerPrincipal(conf.get(serverKey),
- server.getCanonicalHostName().toLowerCase());
+ return SecurityUtil.getServerPrincipal(conf.get(serverKey), hostname);
}
@Override
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/SecurityConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/SecurityConstants.java
index b5540d80d2e..3e387e83356 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/SecurityConstants.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/SecurityConstants.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.hbase.security;
+import org.apache.hadoop.hbase.HBaseInterfaceAudience;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -34,7 +35,18 @@ public final class SecurityConstants {
public static final String REGIONSERVER_KRB_PRINCIPAL = "hbase.regionserver.kerberos.principal";
public static final String REGIONSERVER_KRB_KEYTAB_FILE = "hbase.regionserver.keytab.file";
+ /**
+ * This config is for experts: don't set its value unless you really know what you are doing.
+ * When set to true, HBase client using SASL Kerberos will skip reverse DNS lookup and use provided
+ * hostname of the destination for the principal instead. See https://issues.apache.org/jira/browse/HBASE-25665
+ * for more details.
+ */
+ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
+ public static final String UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS =
+ "hbase.unsafe.client.kerberos.hostname.disable.reversedns";
+ public static final boolean DEFAULT_UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS = false;
+
private SecurityConstants() {
// Can't be instantiated with this ctor.
}
-}
\ No newline at end of file
+}
diff --git a/hbase-common/src/main/resources/hbase-default.xml b/hbase-common/src/main/resources/hbase-default.xml
index 83427f04f15..adffaac7b7f 100644
--- a/hbase-common/src/main/resources/hbase-default.xml
+++ b/hbase-common/src/main/resources/hbase-default.xml
@@ -1201,6 +1201,14 @@ possible configurations would overwhelm and obscure the important.
be used as a temporary measure while converting clients over to secure authentication. It
MUST BE DISABLED for secure operation.
+
+ hbase.unsafe.client.kerberos.hostname.disable.reversedns
+ false
+ This config is for experts: don't set its value unless you really know what you are doing.
+ When set to true, HBase client using SASL Kerberos will skip reverse DNS lookup and use provided
+ hostname of the destination for the principal instead. See https://issues.apache.org/jira/browse/HBASE-25665
+ for more details.
+
hbase.display.keys
true
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java
index 958869982b0..09f21d7e650 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java
@@ -22,6 +22,7 @@ import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.newBlocking
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
+import static org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders.SELECTOR_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -29,12 +30,16 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.conf.Configuration;
@@ -52,11 +57,18 @@ import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.ipc.RpcServerFactory;
import org.apache.hadoop.hbase.ipc.RpcServerInterface;
import org.apache.hadoop.hbase.ipc.SimpleRpcServer;
+import org.apache.hadoop.hbase.security.provider.AuthenticationProviderSelector;
+import org.apache.hadoop.hbase.security.provider.BuiltInProviderSelector;
+import org.apache.hadoop.hbase.security.provider.SaslAuthMethod;
+import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -76,6 +88,7 @@ import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService;
import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos;
import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation;
@RunWith(Parameterized.class)
@Category({ SecurityTests.class, LargeTests.class })
@@ -164,6 +177,117 @@ public class TestSecureIPC {
callRpcService(User.create(ugi2));
}
+ @Test
+ public void testRpcCallWithEnabledKerberosSaslAuth_CanonicalHostname() throws Exception {
+ UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
+
+ // check that the login user is okay:
+ assertSame(ugi2, ugi);
+ assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
+ assertEquals(krbPrincipal, ugi.getUserName());
+
+ enableCanonicalHostnameTesting(clientConf, "localhost");
+ clientConf.setBoolean(
+ SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, false);
+ clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm());
+
+ callRpcService(User.create(ugi2));
+ }
+
+ @Test
+ public void testRpcCallWithEnabledKerberosSaslAuth_NoCanonicalHostname() throws Exception {
+ UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
+
+ // check that the login user is okay:
+ assertSame(ugi2, ugi);
+ assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
+ assertEquals(krbPrincipal, ugi.getUserName());
+
+ enableCanonicalHostnameTesting(clientConf, "127.0.0.1");
+ clientConf.setBoolean(
+ SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, true);
+ clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm());
+
+ callRpcService(User.create(ugi2));
+ }
+
+ private static void enableCanonicalHostnameTesting(Configuration conf, String canonicalHostname) {
+ conf.setClass(SELECTOR_KEY,
+ CanonicalHostnameTestingAuthenticationProviderSelector.class,
+ AuthenticationProviderSelector.class);
+ conf.set(CanonicalHostnameTestingAuthenticationProviderSelector.CANONICAL_HOST_NAME_KEY,
+ canonicalHostname);
+ }
+
+ public static class CanonicalHostnameTestingAuthenticationProviderSelector extends
+ BuiltInProviderSelector {
+ private static final String CANONICAL_HOST_NAME_KEY =
+ "CanonicalHostnameTestingAuthenticationProviderSelector.canonicalHostName";
+
+ @Override
+ public Pair> selectProvider(
+ String clusterId, User user) {
+ final Pair> pair =
+ super.selectProvider(clusterId, user);
+ pair.setFirst(createCanonicalHostNameTestingProvider(pair.getFirst()));
+ return pair;
+ }
+
+ SaslClientAuthenticationProvider createCanonicalHostNameTestingProvider(
+ SaslClientAuthenticationProvider delegate) {
+ return new SaslClientAuthenticationProvider() {
+ @Override
+ public SaslClient createClient(Configuration conf, InetAddress serverAddr,
+ SecurityInfo securityInfo, Token extends TokenIdentifier> token,
+ boolean fallbackAllowed, Map saslProps) throws IOException {
+ final String s =
+ conf.get(CANONICAL_HOST_NAME_KEY);
+ if (s != null) {
+ try {
+ final Field canonicalHostName = InetAddress.class.getDeclaredField("canonicalHostName");
+ canonicalHostName.setAccessible(true);
+ canonicalHostName.set(serverAddr, s);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return delegate.createClient(conf, serverAddr, securityInfo, token, fallbackAllowed, saslProps);
+ }
+
+ @Override
+ public UserInformation getUserInfo(User user) {
+ return delegate.getUserInfo(user);
+ }
+
+ @Override
+ public UserGroupInformation getRealUser(User ugi) {
+ return delegate.getRealUser(ugi);
+ }
+
+ @Override
+ public boolean canRetry() {
+ return delegate.canRetry();
+ }
+
+ @Override
+ public void relogin() throws IOException {
+ delegate.relogin();
+ }
+
+ @Override
+ public SaslAuthMethod getSaslAuthMethod() {
+ return delegate.getSaslAuthMethod();
+ }
+
+ @Override
+ public String getTokenKind() {
+ return delegate.getTokenKind();
+ }
+ };
+ }
+ }
+
@Test
public void testRpcFallbackToSimpleAuth() throws Exception {
String clientUsername = "testuser";