diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index 87af79f4c8ca..ce5bf0d01f61 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -539,6 +539,7 @@ public Boolean run() throws IOException { // fall back to simple auth because server told us so. // do not change authMethod and useSasl here, we should start from secure when // reconnecting because regionserver may change its sasl config after restart. + saslRpcClient = null; } } this.in = new DataInputStream(new BufferedInputStream(inStream)); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java index 0394bb0f2a3b..ace1c38ab22a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java @@ -32,6 +32,7 @@ import javax.security.sasl.SaslException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES; +import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.ipc.RemoteException; @@ -107,12 +108,9 @@ public boolean saslConnect(InputStream inS, OutputStream outS) throws IOExceptio int len = inStream.readInt(); if (len == SaslUtil.SWITCH_TO_SIMPLE_AUTH) { if (!fallbackAllowed) { - throw new IOException("Server asks us to fall back to SIMPLE auth, " - + "but this client is configured to only allow secure connections."); - } - if (LOG.isDebugEnabled()) { - LOG.debug("Server asks us to fall back to simple auth."); + throw new FallbackDisallowedException(); } + LOG.debug("Server asks us to fall back to simple auth."); dispose(); return false; } diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKConfig.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKConfig.java index d70fa0178a51..5c24418214b7 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKConfig.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKConfig.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.Properties; -import java.util.Set; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.util.StringUtils; 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 67d9803bf298..22f44edc70bd 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 @@ -23,17 +23,22 @@ import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting; import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.setSecuredConfiguration; import static org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders.SELECTOR_KEY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -48,7 +53,9 @@ import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.exceptions.ConnectionClosedException; import org.apache.hadoop.hbase.ipc.BlockingRpcClient; +import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; import org.apache.hadoop.hbase.ipc.FifoRpcScheduler; import org.apache.hadoop.hbase.ipc.NettyRpcClient; import org.apache.hadoop.hbase.ipc.NettyRpcServer; @@ -56,7 +63,6 @@ import org.apache.hadoop.hbase.ipc.RpcClientFactory; 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; @@ -289,7 +295,7 @@ public String getTokenKind() { } @Test - public void testRpcFallbackToSimpleAuth() throws Exception { + public void testRpcServerFallbackToSimpleAuth() throws Exception { String clientUsername = "testuser"; UserGroupInformation clientUgi = UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); @@ -304,6 +310,59 @@ public void testRpcFallbackToSimpleAuth() throws Exception { callRpcService(User.create(clientUgi)); } + @Test + public void testRpcServerDisallowFallbackToSimpleAuth() throws Exception { + String clientUsername = "testuser"; + UserGroupInformation clientUgi = + UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); + + // check that the client user is insecure + assertNotSame(ugi, clientUgi); + assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod()); + assertEquals(clientUsername, clientUgi.getUserName()); + + clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); + serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, false); + IOException error = + assertThrows(IOException.class, () -> callRpcService(User.create(clientUgi))); + // server just closes the connection, so we could get broken pipe, or EOF, or connection closed + if (error.getMessage() == null || !error.getMessage().contains("Broken pipe")) { + assertThat(error, + either(instanceOf(EOFException.class)).or(instanceOf(ConnectionClosedException.class))); + } + } + + @Test + public void testRpcClientFallbackToSimpleAuth() throws Exception { + String serverUsername = "testuser"; + UserGroupInformation serverUgi = + UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); + // check that the server user is insecure + assertNotSame(ugi, serverUgi); + assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); + assertEquals(serverUsername, serverUgi.getUserName()); + + serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); + clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, true); + callRpcService(User.create(serverUgi), User.create(ugi)); + } + + @Test + public void testRpcClientDisallowFallbackToSimpleAuth() throws Exception { + String serverUsername = "testuser"; + UserGroupInformation serverUgi = + UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); + // check that the server user is insecure + assertNotSame(ugi, serverUgi); + assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); + assertEquals(serverUsername, serverUgi.getUserName()); + + serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); + clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, false); + assertThrows(FallbackDisallowedException.class, + () -> callRpcService(User.create(serverUgi), User.create(ugi))); + } + private void setRpcProtection(String clientProtection, String serverProtection) { clientConf.set("hbase.rpc.protection", clientProtection); serverConf.set("hbase.rpc.protection", serverProtection); @@ -315,25 +374,25 @@ private void setRpcProtection(String clientProtection, String serverProtection) @Test public void testSaslWithCommonQop() throws Exception { setRpcProtection("privacy,authentication", "authentication"); - callRpcService(User.create(ugi)); + callRpcService(); setRpcProtection("authentication", "privacy,authentication"); - callRpcService(User.create(ugi)); + callRpcService(); setRpcProtection("integrity,authentication", "privacy,authentication"); - callRpcService(User.create(ugi)); + callRpcService(); setRpcProtection("integrity,authentication", "integrity,authentication"); - callRpcService(User.create(ugi)); + callRpcService(); setRpcProtection("privacy,authentication", "privacy,authentication"); - callRpcService(User.create(ugi)); + callRpcService(); } @Test public void testSaslNoCommonQop() throws Exception { setRpcProtection("integrity", "privacy"); - SaslException se = assertThrows(SaslException.class, () -> callRpcService(User.create(ugi))); + SaslException se = assertThrows(SaslException.class, () -> callRpcService()); assertEquals("No common protection layer between client and server", se.getMessage()); } @@ -344,7 +403,7 @@ public void testSaslNoCommonQop() throws Exception { public void testSaslWithCryptoAES() throws Exception { setRpcProtection("privacy", "privacy"); setCryptoAES("true", "true"); - callRpcService(User.create(ugi)); + callRpcService(); } /** @@ -355,11 +414,11 @@ public void testDifferentConfWithCryptoAES() throws Exception { setRpcProtection("privacy", "privacy"); setCryptoAES("false", "true"); - callRpcService(User.create(ugi)); + callRpcService(); setCryptoAES("true", "false"); try { - callRpcService(User.create(ugi)); + callRpcService(); fail("The exception should be thrown out for the rpc timeout."); } catch (Exception e) { // ignore the expected exception @@ -384,7 +443,7 @@ private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krb * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown from * the stub, this function will throw root cause of that exception. */ - private void callRpcService(User clientUser) throws Exception { + private void callRpcService(User serverUser, User clientUser) throws Exception { SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class); Mockito.when(securityInfoMock.getServerPrincipal()) .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL); @@ -392,10 +451,12 @@ private void callRpcService(User clientUser) throws Exception { InetSocketAddress isa = new InetSocketAddress(HOST, 0); - RpcServerInterface rpcServer = RpcServerFactory.createRpcServer(null, "AbstractTestSecureIPC", - Lists - .newArrayList(new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)), - isa, serverConf, new FifoRpcScheduler(serverConf, 1)); + RpcServer rpcServer = serverUser.getUGI() + .doAs((PrivilegedExceptionAction< + RpcServer>) () -> RpcServerFactory.createRpcServer(null, "AbstractTestSecureIPC", + Lists.newArrayList( + new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)), + isa, serverConf, new FifoRpcScheduler(serverConf, 1))); rpcServer.start(); try (RpcClient rpcClient = RpcClientFactory.createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString())) { @@ -425,6 +486,14 @@ public void uncaughtException(Thread th, Throwable ex) { } } + private void callRpcService(User clientUser) throws Exception { + callRpcService(User.create(ugi), clientUser); + } + + private void callRpcService() throws Exception { + callRpcService(User.create(ugi)); + } + public static class TestThread extends Thread { private final BlockingInterface stub;