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:
parent
e6cc5eb2f0
commit
c1f0634462
|
@ -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 {
|
||||||
|
if (provider.canRetry()) {
|
||||||
provider.relogin();
|
provider.relogin();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Relogin failed", e);
|
LOG.warn("Relogin failed", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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<>();
|
|
||||||
// Master-based connection will fail to ask the master its cluster ID
|
|
||||||
// as a part of creating the Connection.
|
|
||||||
params.add(new Pair<String, Class<? extends Exception>>(
|
|
||||||
MasterRegistry.class.getName(), MasterRegistryFetchException.class));
|
|
||||||
// ZK based connection will fail on the master RPC
|
|
||||||
params.add(new Pair<String, Class<? extends Exception>>(
|
|
||||||
// ZKConnectionRegistry is package-private
|
|
||||||
HConstants.ZK_CONNECTION_REGISTRY_CLASS, RetriesExhaustedException.class));
|
|
||||||
|
|
||||||
params.forEach((pair) -> {
|
|
||||||
LOG.info("Running negative authentication test for client registry {}, expecting {}",
|
|
||||||
pair.getFirst(), pair.getSecond().getName());
|
|
||||||
// Validate that we can read that record back out as the user with our custom auth'n
|
// Validate that we can read that record back out as the user with our custom auth'n
|
||||||
final Configuration clientConf = new Configuration(CONF);
|
final Configuration clientConf = new Configuration(CONF);
|
||||||
clientConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3);
|
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)) {
|
try (Connection conn = ConnectionFactory.createConnection(clientConf)) {
|
||||||
UserGroupInformation user1 = UserGroupInformation.createUserForTesting(
|
UserGroupInformation user1 = UserGroupInformation.createUserForTesting(
|
||||||
"user1", new String[0]);
|
"user1", new String[0]);
|
||||||
user1.addToken(
|
user1.addToken(
|
||||||
ShadeClientTokenUtil.obtainToken(conn, "user1", "not a real password".toCharArray()));
|
ShadeClientTokenUtil.obtainToken(conn, "user1", "not a real password".toCharArray()));
|
||||||
|
|
||||||
LOG.info("Executing request to HBase Master which should fail");
|
|
||||||
user1.doAs(new PrivilegedExceptionAction<Void>() {
|
user1.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
@Override public Void run() throws Exception {
|
@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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
try (Connection conn = ConnectionFactory.createConnection(clientConf);
|
||||||
Table t = conn.getTable(tableName)) {
|
Table t = conn.getTable(tableName)) {
|
||||||
t.get(new Get(Bytes.toBytes("r1")));
|
t.get(new Get(Bytes.toBytes("r1")));
|
||||||
fail("Should not successfully authenticate with HBase");
|
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;
|
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue