Revert "HBASE-23381 Ensure Netty client receives at least one response before considering SASL negotiation complete"

Incorrect jira issue number

This reverts commit 78eecd3a7d.
This commit is contained in:
Duo Zhang 2020-04-26 10:22:46 +08:00
parent e6cc5eb2f0
commit c1f0634462
7 changed files with 26 additions and 139 deletions

View File

@ -149,10 +149,6 @@ class NettyRpcConnection extends RpcConnection {
if (error instanceof FallbackDisallowedException) { if (error instanceof FallbackDisallowedException) {
return; return;
} }
if (!provider.canRetry()) {
LOG.trace("SASL Provider does not support retries");
return;
}
synchronized (this) { synchronized (this) {
if (reloginInProgress) { if (reloginInProgress) {
return; return;
@ -163,7 +159,9 @@ class NettyRpcConnection extends RpcConnection {
@Override @Override
public void run() { public void run() {
try { try {
provider.relogin(); if (provider.canRetry()) {
provider.relogin();
}
} catch (IOException e) { } catch (IOException e) {
LOG.warn("Relogin failed", e); LOG.warn("Relogin failed", e);
} }

View File

@ -88,24 +88,12 @@ public abstract class AbstractHBaseSaslRpcClient {
} }
} }
/**
* Computes the initial response a client sends to a server to begin the SASL
* challenge/response handshake. If the client's SASL mechanism does not require
* that an initial response is sent to begin the handshake, this method will return
* a null byte array, indicating no initial response needs to be sent by this client.
*
* It is unclear as to whether all SASL implementations will return a non-empty initial
* response, so this implementation is written such that this is allowed. All known
* SASL mechanism implementations in the JDK provide non-empty initial responses.
*
* @return The client's initial response to send the server (which may be empty), or null
* if this implementation does not require an initial response to be sent.
*/
public byte[] getInitialResponse() throws SaslException { public byte[] getInitialResponse() throws SaslException {
if (saslClient.hasInitialResponse()) { if (saslClient.hasInitialResponse()) {
return saslClient.evaluateChallenge(EMPTY_TOKEN); return saslClient.evaluateChallenge(EMPTY_TOKEN);
} else {
return EMPTY_TOKEN;
} }
return null;
} }
public boolean isComplete() { public boolean isComplete() {

View File

@ -54,8 +54,6 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler<
private final Configuration conf; private final Configuration conf;
private final SaslClientAuthenticationProvider provider;
// flag to mark if Crypto AES encryption is enable // flag to mark if Crypto AES encryption is enable
private boolean needProcessConnectionHeader = false; private boolean needProcessConnectionHeader = false;
@ -70,7 +68,6 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler<
this.saslPromise = saslPromise; this.saslPromise = saslPromise;
this.ugi = ugi; this.ugi = ugi;
this.conf = conf; this.conf = conf;
this.provider = provider;
this.saslRpcClient = new NettyHBaseSaslRpcClient(conf, provider, token, serverAddr, this.saslRpcClient = new NettyHBaseSaslRpcClient(conf, provider, token, serverAddr,
securityInfo, fallbackAllowed, conf.get( securityInfo, fallbackAllowed, conf.get(
"hbase.rpc.protection", SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase())); "hbase.rpc.protection", SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase()));
@ -87,11 +84,6 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler<
return; return;
} }
// HBASE-23881 Clearly log when the client thinks that the SASL negotiation is complete.
if (LOG.isTraceEnabled()) {
LOG.trace("SASL negotiation for {} is complete", provider.getSaslAuthMethod().getName());
}
saslRpcClient.setupSaslHandler(ctx.pipeline()); saslRpcClient.setupSaslHandler(ctx.pipeline());
setCryptoAESOption(); setCryptoAESOption();
@ -121,19 +113,8 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler<
}); });
if (initialResponse != null) { if (initialResponse != null) {
writeResponse(ctx, initialResponse); writeResponse(ctx, initialResponse);
} else {
LOG.trace("SASL initialResponse was null, not sending response to server.");
} }
// HBASE-23881 We do not want to check if the SaslClient thinks the handshake is tryComplete(ctx);
// complete as, at this point, we've not heard a back from the server with it's reply
// to our first challenge response. We should wait for at least one reply
// from the server before calling negotiation complete.
//
// Each SASL mechanism has its own handshake. Some mechanisms calculate a single client buffer
// to be sent to the server while others have multiple exchanges to negotiate authentication. GSSAPI(Kerberos)
// and DIGEST-MD5 both are examples of mechanisms which have multiple steps. Mechanisms which have multiple steps
// will not return true on `SaslClient#isComplete()` until the handshake has fully completed. Mechanisms which
// only send a single buffer may return true on `isComplete()` after that initial response is calculated.
} catch (Exception e) { } catch (Exception e) {
// the exception thrown by handlerAdded will not be passed to the exceptionCaught below // the exception thrown by handlerAdded will not be passed to the exceptionCaught below
// because netty will remove a handler if handlerAdded throws an exception. // because netty will remove a handler if handlerAdded throws an exception.
@ -165,8 +146,6 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler<
}); });
if (response != null) { if (response != null) {
writeResponse(ctx, response); writeResponse(ctx, response);
} else {
LOG.trace("SASL challenge response was empty, not sending response to server.");
} }
tryComplete(ctx); tryComplete(ctx);
} }

View File

@ -62,12 +62,6 @@ public class ShadeSaslClientAuthenticationProvider extends ShadeSaslAuthenticati
return userInfoPB.build(); return userInfoPB.build();
} }
@Override
public boolean canRetry() {
// A static username/password either works or it doesn't. No kind of relogin/retry necessary.
return false;
}
static class ShadeSaslClientCallbackHandler implements CallbackHandler { static class ShadeSaslClientCallbackHandler implements CallbackHandler {
private final String username; private final String username;
private final char[] password; private final char[] password;

View File

@ -141,6 +141,7 @@ public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticati
@Override public void handle(Callback[] callbacks) @Override public void handle(Callback[] callbacks)
throws InvalidToken, UnsupportedCallbackException { throws InvalidToken, UnsupportedCallbackException {
LOG.info("SaslServerCallbackHandler called", new Exception());
NameCallback nc = null; NameCallback nc = null;
PasswordCallback pc = null; PasswordCallback pc = null;
AuthorizeCallback ac = null; AuthorizeCallback ac = null;

View File

@ -17,7 +17,6 @@
*/ */
package org.apache.hadoop.hbase.security.provider.example; package org.apache.hadoop.hbase.security.provider.example;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -27,12 +26,8 @@ import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -42,6 +37,7 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HConstants;
@ -52,14 +48,11 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.MasterRegistry;
import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.RetriesExhaustedException;
import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.exceptions.MasterRegistryFetchException;
import org.apache.hadoop.hbase.security.HBaseKerberosUtils; import org.apache.hadoop.hbase.security.HBaseKerberosUtils;
import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders;
import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders;
@ -68,12 +61,8 @@ import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SecurityTests;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.minikdc.MiniKdc; import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.SecretManager.InvalidToken;
import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -82,18 +71,14 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import org.junit.rules.TestName; import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Category({MediumTests.class, SecurityTests.class}) @Category({MediumTests.class, SecurityTests.class})
public class TestShadeSaslAuthenticationProvider { public class TestShadeSaslAuthenticationProvider {
private static final Logger LOG = LoggerFactory.getLogger(TestShadeSaslAuthenticationProvider.class);
@ClassRule @ClassRule
public static final HBaseClassTestRule CLASS_RULE = public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestShadeSaslAuthenticationProvider.class); HBaseClassTestRule.forClass(TestShadeSaslAuthenticationProvider.class);
private static final char[] USER1_PASSWORD = "foobarbaz".toCharArray(); private static final char[] USER1_PASSWORD = "foobarbaz".toCharArray();
static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile, static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile,
@ -235,84 +220,26 @@ public class TestShadeSaslAuthenticationProvider {
} }
} }
@Test @Test(expected = DoNotRetryIOException.class)
public void testNegativeAuthentication() throws Exception { public void testNegativeAuthentication() throws Exception {
List<Pair<String, Class<? extends Exception>>> params = new ArrayList<>(); // Validate that we can read that record back out as the user with our custom auth'n
// Master-based connection will fail to ask the master its cluster ID final Configuration clientConf = new Configuration(CONF);
// as a part of creating the Connection. clientConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
params.add(new Pair<String, Class<? extends Exception>>( try (Connection conn = ConnectionFactory.createConnection(clientConf)) {
MasterRegistry.class.getName(), MasterRegistryFetchException.class)); UserGroupInformation user1 = UserGroupInformation.createUserForTesting(
// ZK based connection will fail on the master RPC "user1", new String[0]);
params.add(new Pair<String, Class<? extends Exception>>( user1.addToken(
// ZKConnectionRegistry is package-private ShadeClientTokenUtil.obtainToken(conn, "user1", "not a real password".toCharArray()));
HConstants.ZK_CONNECTION_REGISTRY_CLASS, RetriesExhaustedException.class)); user1.doAs(new PrivilegedExceptionAction<Void>() {
@Override public Void run() throws Exception {
params.forEach((pair) -> { try (Connection conn = ConnectionFactory.createConnection(clientConf);
LOG.info("Running negative authentication test for client registry {}, expecting {}", Table t = conn.getTable(tableName)) {
pair.getFirst(), pair.getSecond().getName()); t.get(new Get(Bytes.toBytes("r1")));
// Validate that we can read that record back out as the user with our custom auth'n fail("Should not successfully authenticate with HBase");
final Configuration clientConf = new Configuration(CONF);
clientConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
clientConf.set(HConstants.CLIENT_CONNECTION_REGISTRY_IMPL_CONF_KEY, pair.getFirst());
try (Connection conn = ConnectionFactory.createConnection(clientConf)) {
UserGroupInformation user1 = UserGroupInformation.createUserForTesting(
"user1", new String[0]);
user1.addToken(
ShadeClientTokenUtil.obtainToken(conn, "user1", "not a real password".toCharArray()));
LOG.info("Executing request to HBase Master which should fail");
user1.doAs(new PrivilegedExceptionAction<Void>() {
@Override public Void run() throws Exception {
try (Connection conn = ConnectionFactory.createConnection(clientConf);) {
conn.getAdmin().listTableDescriptors();
fail("Should not successfully authenticate with HBase");
} catch (Exception e) {
LOG.info("Caught exception in negative Master connectivity test", e);
assertEquals("Found unexpected exception", pair.getSecond(), e.getClass());
validateRootCause(Throwables.getRootCause(e));
}
return null; return null;
} }
}); }
});
LOG.info("Executing request to HBase RegionServer which should fail");
user1.doAs(new PrivilegedExceptionAction<Void>() {
@Override public Void run() throws Exception {
// A little contrived because, with MasterRegistry, we'll still fail on talking
// to the HBase master before trying to talk to a RegionServer.
try (Connection conn = ConnectionFactory.createConnection(clientConf);
Table t = conn.getTable(tableName)) {
t.get(new Get(Bytes.toBytes("r1")));
fail("Should not successfully authenticate with HBase");
} catch (Exception e) {
LOG.info("Caught exception in negative RegionServer connectivity test", e);
assertEquals("Found unexpected exception", pair.getSecond(), e.getClass());
validateRootCause(Throwables.getRootCause(e));
}
return null;
}
});
} catch (InterruptedException e) {
LOG.error("Caught interrupted exception", e);
Thread.currentThread().interrupt();
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
void validateRootCause(Throwable rootCause) {
LOG.info("Root cause was", rootCause);
if (rootCause instanceof RemoteException) {
RemoteException re = (RemoteException) rootCause;
IOException actualException = re.unwrapRemoteException();
assertEquals(InvalidToken.class, actualException.getClass());
} else {
StringWriter writer = new StringWriter();
rootCause.printStackTrace(new PrintWriter(writer));
String text = writer.toString();
assertTrue("Message did not contain expected text", text.contains(InvalidToken.class.getName()));
} }
} }
} }

View File

@ -373,7 +373,7 @@ abstract class ServerRpcConnection implements Closeable {
String clientIP = this.toString(); String clientIP = this.toString();
// attempting user could be null // attempting user could be null
RpcServer.AUDITLOG RpcServer.AUDITLOG
.warn("{}{}: {}", RpcServer.AUTH_FAILED_FOR, clientIP, saslServer.getAttemptingUser()); .warn("{} {}: {}", RpcServer.AUTH_FAILED_FOR, clientIP, saslServer.getAttemptingUser());
throw e; throw e;
} }
if (replyToken != null) { if (replyToken != null) {