HBASE-14700 Support a permissive mode for secure clusters to allow SIMPLE auth clients

This commit is contained in:
Gary Helmling 2015-10-30 19:45:46 -07:00
parent 4534f8ed0c
commit 683f84e6a2
8 changed files with 151 additions and 19 deletions

View File

@ -1021,6 +1021,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.</description>
</property>
<property>
<name>hbase.ipc.server.fallback-to-simple-auth-allowed</name>
<value>false</value>
<description>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.</description>
</property>
<property>
<name>hbase.display.keys</name>
<value>true</value>

View File

@ -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();
/**

View File

@ -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();

View File

@ -53,6 +53,10 @@ public class MetricsHBaseServer {
source.authenticationSuccess();
}
void authenticationFallback() {
source.authenticationFallback();
}
void sentBytes(long count) {
source.sentBytes(count);
}

View File

@ -80,6 +80,7 @@ import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.TableName;
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;
@ -156,7 +157,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
@ -167,6 +168,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
@ -1253,6 +1261,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;
@ -1329,19 +1340,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,
@ -1520,10 +1533,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(
@ -1670,6 +1688,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);
@ -2024,10 +2048,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.

View File

@ -885,6 +885,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);
}
/**

View File

@ -66,6 +66,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.exceptions.FailedSanityCheckException;
import org.apache.hadoop.hbase.exceptions.MergeRegionException;
import org.apache.hadoop.hbase.exceptions.OperationConflictException;
@ -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. */
@ -982,6 +984,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);
}

View File

@ -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;
@ -100,21 +101,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<? extends RpcClient> 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:
@ -122,8 +129,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<? extends RpcClient> 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);
@ -141,7 +168,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) {
@ -150,7 +177,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<Integer> results = new ArrayList<Integer>();
@ -164,4 +191,26 @@ public class TestSecureRPC {
rpcServer.stop();
}
}
public void testRpcFallbackToSimpleAuth(Class<? extends RpcClient> 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);
}
}