diff --git a/hbase-common/src/main/resources/hbase-default.xml b/hbase-common/src/main/resources/hbase-default.xml index 1efcdd8432b..759c3694883 100644 --- a/hbase-common/src/main/resources/hbase-default.xml +++ b/hbase-common/src/main/resources/hbase-default.xml @@ -922,6 +922,17 @@ possible configurations would overwhelm and obscure the important. When false (the default), the client will not allow the fallback to SIMPLE authentication, and will abort the connection. + + hbase.ipc.server.fallback-to-simple-auth-allowed + false + When a server is configured to require secure connections, it will + reject connection attempts from clients using SASL SIMPLE (unsecure) authentication. + This setting allows secure servers to accept SASL SIMPLE connections from clients + when the client requests. When false (the default), the server will not allow the fallback + to SIMPLE authentication, and will reject the connection. WARNING: This setting should ONLY + be used as a temporary measure while converting clients over to secure authentication. It + MUST BE DISABLED for secure operation. + hbase.coprocessor.enabled true diff --git a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSource.java b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSource.java index 36bd6439de8..5cf71f37eaa 100644 --- a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSource.java +++ b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSource.java @@ -34,6 +34,9 @@ public interface MetricsHBaseServerSource extends BaseSource { String AUTHENTICATION_FAILURES_NAME = "authenticationFailures"; String AUTHENTICATION_FAILURES_DESC = "Number of authentication failures."; + String AUTHENTICATION_FALLBACKS_NAME = "authenticationFallbacks"; + String AUTHENTICATION_FALLBACKS_DESC = + "Number of fallbacks to insecure authentication."; String SENT_BYTES_NAME = "sentBytes"; String SENT_BYTES_DESC = "Number of bytes sent."; String RECEIVED_BYTES_NAME = "receivedBytes"; @@ -80,6 +83,8 @@ public interface MetricsHBaseServerSource extends BaseSource { void authenticationFailure(); + void authenticationFallback(); + void exception(); /** diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java index fe84733ef6d..78b1c6688bb 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java @@ -36,6 +36,7 @@ public class MetricsHBaseServerSourceImpl extends BaseSourceImpl private final MutableCounterLong authorizationFailures; private final MutableCounterLong authenticationSuccesses; private final MutableCounterLong authenticationFailures; + private final MutableCounterLong authenticationFallbacks; private final MutableCounterLong sentBytes; private final MutableCounterLong receivedBytes; @@ -85,6 +86,8 @@ public class MetricsHBaseServerSourceImpl extends BaseSourceImpl AUTHENTICATION_SUCCESSES_NAME, AUTHENTICATION_SUCCESSES_DESC, 0L); this.authenticationFailures = this.getMetricsRegistry().newCounter(AUTHENTICATION_FAILURES_NAME, AUTHENTICATION_FAILURES_DESC, 0L); + this.authenticationFallbacks = this.getMetricsRegistry().newCounter( + AUTHENTICATION_FALLBACKS_NAME, AUTHENTICATION_FALLBACKS_DESC, 0L); this.sentBytes = this.getMetricsRegistry().newCounter(SENT_BYTES_NAME, SENT_BYTES_DESC, 0L); this.receivedBytes = this.getMetricsRegistry().newCounter(RECEIVED_BYTES_NAME, @@ -116,6 +119,11 @@ public class MetricsHBaseServerSourceImpl extends BaseSourceImpl authenticationFailures.incr(); } + @Override + public void authenticationFallback() { + authenticationFallbacks.incr(); + } + @Override public void exception() { exceptions.incr(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServer.java index ceef0df23fa..d276503ce4d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServer.java @@ -53,6 +53,10 @@ public class MetricsHBaseServer { source.authenticationSuccess(); } + void authenticationFallback() { + source.authenticationFallback(); + } + void sentBytes(long count) { source.sentBytes(count); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java index af422bf6cc5..30e5e867d96 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java @@ -81,6 +81,7 @@ import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.NeedUnmanagedConnectionException; import org.apache.hadoop.hbase.client.Operation; import org.apache.hadoop.hbase.codec.Codec; +import org.apache.hadoop.hbase.conf.ConfigurationObserver; import org.apache.hadoop.hbase.exceptions.RegionMovedException; import org.apache.hadoop.hbase.io.ByteBufferOutputStream; import org.apache.hadoop.hbase.io.BoundedByteBufferPool; @@ -157,7 +158,7 @@ import com.google.protobuf.TextFormat; */ @InterfaceAudience.LimitedPrivate({HBaseInterfaceAudience.COPROC, HBaseInterfaceAudience.PHOENIX}) @InterfaceStability.Evolving -public class RpcServer implements RpcServerInterface { +public class RpcServer implements RpcServerInterface, ConfigurationObserver { // LOG is being used in CallRunner and the log level is being changed in tests public static final Log LOG = LogFactory.getLog(RpcServer.class); private static final CallQueueTooBigException CALL_QUEUE_TOO_BIG_EXCEPTION @@ -168,6 +169,12 @@ public class RpcServer implements RpcServerInterface { public static final byte CURRENT_VERSION = 0; + /** + * Whether we allow a fallback to SIMPLE auth for insecure clients when security is enabled. + */ + public static final String FALLBACK_TO_INSECURE_CLIENT_AUTH = + "hbase.ipc.server.fallback-to-simple-auth-allowed"; + /** * How many calls/handler are allowed in the queue. */ @@ -276,6 +283,7 @@ public class RpcServer implements RpcServerInterface { private final BoundedByteBufferPool reservoir; + private volatile boolean allowFallbackToSimpleAuth; /** * Datastructure that holds all necessary to a method invocation and then afterward, carries @@ -1237,6 +1245,9 @@ public class RpcServer implements RpcServerInterface { private final Call saslCall = new Call(SASL_CALLID, this.service, null, null, null, null, this, null, 0, null, null); + // was authentication allowed with a fallback to simple auth + private boolean authenticatedWithFallback; + public UserGroupInformation attemptingUser = null; // user name before auth protected User user = null; protected UserGroupInformation ugi = null; @@ -1313,19 +1324,21 @@ public class RpcServer implements RpcServerInterface { private UserGroupInformation getAuthorizedUgi(String authorizedId) throws IOException { + UserGroupInformation authorizedUgi; if (authMethod == AuthMethod.DIGEST) { TokenIdentifier tokenId = HBaseSaslRpcServer.getIdentifier(authorizedId, secretManager); - UserGroupInformation ugi = tokenId.getUser(); - if (ugi == null) { + authorizedUgi = tokenId.getUser(); + if (authorizedUgi == null) { throw new AccessDeniedException( "Can't retrieve username from tokenIdentifier."); } - ugi.addTokenIdentifier(tokenId); - return ugi; + authorizedUgi.addTokenIdentifier(tokenId); } else { - return UserGroupInformation.createRemoteUser(authorizedId); + authorizedUgi = UserGroupInformation.createRemoteUser(authorizedId); } + authorizedUgi.setAuthenticationMethod(authMethod.authenticationMethod.getAuthMethod()); + return authorizedUgi; } private void saslReadAndProcess(byte[] saslToken) throws IOException, @@ -1504,10 +1517,15 @@ public class RpcServer implements RpcServerInterface { return doBadPreambleHandling(msg, new BadAuthException(msg)); } if (isSecurityEnabled && authMethod == AuthMethod.SIMPLE) { - AccessDeniedException ae = new AccessDeniedException("Authentication is required"); - setupResponse(authFailedResponse, authFailedCall, ae, ae.getMessage()); - responder.doRespond(authFailedCall); - throw ae; + if (allowFallbackToSimpleAuth) { + metrics.authenticationFallback(); + authenticatedWithFallback = true; + } else { + AccessDeniedException ae = new AccessDeniedException("Authentication is required"); + setupResponse(authFailedResponse, authFailedCall, ae, ae.getMessage()); + responder.doRespond(authFailedCall); + throw ae; + } } if (!isSecurityEnabled && authMethod != AuthMethod.SIMPLE) { doRawSaslReply(SaslStatus.SUCCESS, new IntWritable( @@ -1654,6 +1672,12 @@ public class RpcServer implements RpcServerInterface { if (ugi != null) { ugi.setAuthenticationMethod(AuthMethod.SIMPLE.authenticationMethod); } + // audit logging for SASL authenticated users happens in saslReadAndProcess() + if (authenticatedWithFallback) { + LOG.warn("Allowed fallback to SIMPLE auth for " + ugi + + " connecting from " + getHostAddress()); + } + AUDITLOG.info(AUTH_SUCCESSFUL_FOR + ugi); } else { // user is authenticated ugi.setAuthenticationMethod(authMethod.authenticationMethod); @@ -2010,10 +2034,31 @@ public class RpcServer implements RpcServerInterface { if (isSecurityEnabled) { HBaseSaslRpcServer.init(conf); } + initReconfigurable(conf); + this.scheduler = scheduler; this.scheduler.init(new RpcSchedulerContext(this)); } + @Override + public void onConfigurationChange(Configuration newConf) { + initReconfigurable(newConf); + } + + private void initReconfigurable(Configuration confToLoad) { + this.allowFallbackToSimpleAuth = confToLoad.getBoolean(FALLBACK_TO_INSECURE_CLIENT_AUTH, false); + if (isSecurityEnabled && allowFallbackToSimpleAuth) { + LOG.warn("********* WARNING! *********"); + LOG.warn("This server is configured to allow connections from INSECURE clients"); + LOG.warn("(" + FALLBACK_TO_INSECURE_CLIENT_AUTH + " = true)."); + LOG.warn("While this option is enabled, client identities cannot be secured, and user"); + LOG.warn("impersonation is possible!"); + LOG.warn("For secure operation, please disable SIMPLE authentication as soon as possible,"); + LOG.warn("by setting " + FALLBACK_TO_INSECURE_CLIENT_AUTH + " = false in hbase-site.xml"); + LOG.warn("****************************"); + } + } + /** * Subclasses of HBaseServer can override this to provide their own * Connection implementations. diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java index 5e816565194..775182170fd 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java @@ -876,6 +876,7 @@ public class HRegionServer extends HasThread implements private void registerConfigurationObservers() { // Registering the compactSplitThread object with the ConfigurationManager. configurationManager.registerObserver(this.compactSplitThread); + configurationManager.registerObserver(this.rpcServices); } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java index 47b373aebe1..58df66e99c7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java @@ -67,6 +67,7 @@ import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.RowMutations; import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.conf.ConfigurationObserver; import org.apache.hadoop.hbase.coordination.CloseRegionCoordination; import org.apache.hadoop.hbase.coordination.OpenRegionCoordination; import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException; @@ -192,7 +193,8 @@ import com.google.protobuf.TextFormat; @InterfaceAudience.Private @SuppressWarnings("deprecation") public class RSRpcServices implements HBaseRPCErrorHandler, - AdminService.BlockingInterface, ClientService.BlockingInterface, PriorityFunction { + AdminService.BlockingInterface, ClientService.BlockingInterface, PriorityFunction, + ConfigurationObserver { protected static final Log LOG = LogFactory.getLog(RSRpcServices.class); /** RPC scheduler to use for the region server. */ @@ -900,6 +902,13 @@ public class RSRpcServices implements HBaseRPCErrorHandler, rs.setName(name); } + @Override + public void onConfigurationChange(Configuration newConf) { + if (rpcServer instanceof ConfigurationObserver) { + ((ConfigurationObserver)rpcServer).onConfigurationChange(newConf); + } + } + protected PriorityFunction createPriority() { return new AnnotationReadingPriorityFunction(this); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureRPC.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureRPC.java index a9404084161..ea30c105d7e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureRPC.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureRPC.java @@ -22,6 +22,7 @@ import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileF import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting; import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import java.io.File; @@ -99,21 +100,27 @@ public class TestSecureRPC { testRpcCallWithEnabledKerberosSaslAuth(RpcClientImpl.class); } + @Test + public void testRpcWithInsecureFallback() throws Exception { + testRpcFallbackToSimpleAuth(RpcClientImpl.class); + } + @Test public void testAsyncRpc() throws Exception { testRpcCallWithEnabledKerberosSaslAuth(AsyncRpcClient.class); } + @Test + public void testAsyncRpcWithInsecureFallback() throws Exception { + testRpcFallbackToSimpleAuth(AsyncRpcClient.class); + } + private void testRpcCallWithEnabledKerberosSaslAuth(Class rpcImplClass) throws Exception { String krbKeytab = getKeytabFileForTesting(); String krbPrincipal = getPrincipalForTesting(); - Configuration cnf = new Configuration(); - cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); - UserGroupInformation.setConfiguration(cnf); - UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab); - UserGroupInformation ugi = UserGroupInformation.getLoginUser(); + UserGroupInformation ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal); UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); // check that the login user is okay: @@ -121,8 +128,28 @@ public class TestSecureRPC { assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); assertEquals(krbPrincipal, ugi.getUserName()); + Configuration clientConf = getSecuredConfiguration(); + callRpcService(rpcImplClass, User.create(ugi2), clientConf, false); + } + + private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krbPrincipal) + throws Exception { + Configuration cnf = new Configuration(); + cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); + UserGroupInformation.setConfiguration(cnf); + UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab); + return UserGroupInformation.getLoginUser(); + } + + private void callRpcService(Class rpcImplClass, User clientUser, + Configuration clientConf, boolean allowInsecureFallback) + throws Exception { + Configuration clientConfCopy = new Configuration(clientConf); + clientConfCopy.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, rpcImplClass.getName()); + Configuration conf = getSecuredConfiguration(); - conf.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, rpcImplClass.getName()); + conf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, allowInsecureFallback); + SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class); Mockito.when(securityInfoMock.getServerPrincipal()) .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL); @@ -140,7 +167,7 @@ public class TestSecureRPC { conf, new FifoRpcScheduler(conf, 1)); rpcServer.start(); RpcClient rpcClient = - RpcClientFactory.createClient(conf, HConstants.DEFAULT_CLUSTER_ID.toString()); + RpcClientFactory.createClient(clientConfCopy, HConstants.DEFAULT_CLUSTER_ID.toString()); try { InetSocketAddress address = rpcServer.getListenerAddress(); if (address == null) { @@ -149,7 +176,7 @@ public class TestSecureRPC { BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel( ServerName.valueOf(address.getHostName(), address.getPort(), - System.currentTimeMillis()), User.getCurrent(), 5000); + System.currentTimeMillis()), clientUser, 5000); TestDelayedRpcProtos.TestDelayedService.BlockingInterface stub = TestDelayedRpcProtos.TestDelayedService.newBlockingStub(channel); List results = new ArrayList(); @@ -163,4 +190,26 @@ public class TestSecureRPC { rpcServer.stop(); } } + + public void testRpcFallbackToSimpleAuth(Class rpcImplClass) throws Exception { + String krbKeytab = getKeytabFileForTesting(); + String krbPrincipal = getPrincipalForTesting(); + + UserGroupInformation ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal); + assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); + assertEquals(krbPrincipal, ugi.getUserName()); + + 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()); + + Configuration clientConf = new Configuration(); + clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); + callRpcService(rpcImplClass, User.create(clientUgi), clientConf, true); + } }