HADOOP-6543. Allows secure clients to talk to unsecure clusters. Contributed by Kan Zhang.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@915097 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ffdde40b9f
commit
c5622e5d4d
@ -158,6 +158,9 @@ Trunk (unreleased changes)
|
|||||||
|
|
||||||
HADOOP-6583. Captures authentication and authorization metrics. (ddas)
|
HADOOP-6583. Captures authentication and authorization metrics. (ddas)
|
||||||
|
|
||||||
|
HADOOP-6543. Allows secure clients to talk to unsecure clusters.
|
||||||
|
(Kan Zhang via ddas)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
|
@ -209,8 +209,8 @@ private class Connection extends Thread {
|
|||||||
private String serverPrincipal; // server's krb5 principal name
|
private String serverPrincipal; // server's krb5 principal name
|
||||||
private ConnectionHeader header; // connection header
|
private ConnectionHeader header; // connection header
|
||||||
private final ConnectionId remoteId; // connection id
|
private final ConnectionId remoteId; // connection id
|
||||||
private final AuthMethod authMethod; // authentication method
|
private AuthMethod authMethod; // authentication method
|
||||||
private final boolean useSasl;
|
private boolean useSasl;
|
||||||
private Token<? extends TokenIdentifier> token;
|
private Token<? extends TokenIdentifier> token;
|
||||||
private SaslRpcClient saslRpcClient;
|
private SaslRpcClient saslRpcClient;
|
||||||
|
|
||||||
@ -364,13 +364,13 @@ private synchronized void disposeSasl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setupSaslConnection(final InputStream in2,
|
private synchronized boolean setupSaslConnection(final InputStream in2,
|
||||||
final OutputStream out2)
|
final OutputStream out2)
|
||||||
throws javax.security.sasl.SaslException,IOException,InterruptedException {
|
throws IOException {
|
||||||
try {
|
try {
|
||||||
saslRpcClient = new SaslRpcClient(authMethod, token,
|
saslRpcClient = new SaslRpcClient(authMethod, token,
|
||||||
serverPrincipal);
|
serverPrincipal);
|
||||||
saslRpcClient.saslConnect(in2, out2);
|
return saslRpcClient.saslConnect(in2, out2);
|
||||||
} catch (javax.security.sasl.SaslException je) {
|
} catch (javax.security.sasl.SaslException je) {
|
||||||
if (authMethod == AuthMethod.KERBEROS &&
|
if (authMethod == AuthMethod.KERBEROS &&
|
||||||
UserGroupInformation.isLoginKeytabBased()) {
|
UserGroupInformation.isLoginKeytabBased()) {
|
||||||
@ -378,9 +378,10 @@ private synchronized void setupSaslConnection(final InputStream in2,
|
|||||||
UserGroupInformation.getCurrentUser().reloginFromKeytab();
|
UserGroupInformation.getCurrentUser().reloginFromKeytab();
|
||||||
//try setting up the connection again
|
//try setting up the connection again
|
||||||
try {
|
try {
|
||||||
|
disposeSasl();
|
||||||
saslRpcClient = new SaslRpcClient(authMethod, token,
|
saslRpcClient = new SaslRpcClient(authMethod, token,
|
||||||
serverPrincipal);
|
serverPrincipal);
|
||||||
saslRpcClient.saslConnect(in2, out2);
|
return saslRpcClient.saslConnect(in2, out2);
|
||||||
} catch (javax.security.sasl.SaslException jee) {
|
} catch (javax.security.sasl.SaslException jee) {
|
||||||
UserGroupInformation.
|
UserGroupInformation.
|
||||||
setLastUnsuccessfulAuthenticationAttemptTime
|
setLastUnsuccessfulAuthenticationAttemptTime
|
||||||
@ -437,15 +438,22 @@ private synchronized void setupIOstreams() throws InterruptedException {
|
|||||||
ticket = ticket.getRealUser();
|
ticket = ticket.getRealUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticket.doAs(new PrivilegedExceptionAction<Object>() {
|
if (ticket.doAs(new PrivilegedExceptionAction<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Object run() throws IOException, InterruptedException {
|
public Boolean run() throws IOException {
|
||||||
setupSaslConnection(in2, out2);
|
return setupSaslConnection(in2, out2);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
});
|
})) {
|
||||||
|
// Sasl connect is successful. Let's set up Sasl i/o streams.
|
||||||
inStream = saslRpcClient.getInputStream(inStream);
|
inStream = saslRpcClient.getInputStream(inStream);
|
||||||
outStream = saslRpcClient.getOutputStream(outStream);
|
outStream = saslRpcClient.getOutputStream(outStream);
|
||||||
|
} else {
|
||||||
|
// fall back to simple auth because server told us so.
|
||||||
|
authMethod = AuthMethod.SIMPLE;
|
||||||
|
header = new ConnectionHeader(header.getProtocol(),
|
||||||
|
header.getUgi(), authMethod);
|
||||||
|
useSasl = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (doPing) {
|
if (doPing) {
|
||||||
this.in = new DataInputStream(new BufferedInputStream
|
this.in = new DataInputStream(new BufferedInputStream
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@ -28,8 +27,6 @@
|
|||||||
import org.apache.hadoop.io.Writable;
|
import org.apache.hadoop.io.Writable;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.security.SaslRpcServer.AuthMethod;
|
import org.apache.hadoop.security.SaslRpcServer.AuthMethod;
|
||||||
import org.apache.hadoop.security.token.Token;
|
|
||||||
import org.apache.hadoop.security.token.TokenIdentifier;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IPC connection header sent by the client to the server
|
* The IPC connection header sent by the client to the server
|
||||||
@ -86,16 +83,14 @@ public void readFields(DataInput in) throws IOException {
|
|||||||
public void write(DataOutput out) throws IOException {
|
public void write(DataOutput out) throws IOException {
|
||||||
Text.writeString(out, (protocol == null) ? "" : protocol);
|
Text.writeString(out, (protocol == null) ? "" : protocol);
|
||||||
if (ugi != null) {
|
if (ugi != null) {
|
||||||
if (UserGroupInformation.isSecurityEnabled()) {
|
|
||||||
if (authMethod == AuthMethod.KERBEROS) {
|
if (authMethod == AuthMethod.KERBEROS) {
|
||||||
// Send effective user for Kerberos auth
|
// Send effective user for Kerberos auth
|
||||||
out.writeBoolean(true);
|
out.writeBoolean(true);
|
||||||
out.writeUTF(ugi.getUserName());
|
out.writeUTF(ugi.getUserName());
|
||||||
out.writeBoolean(false);
|
out.writeBoolean(false);
|
||||||
} else {
|
} else if (authMethod == AuthMethod.DIGEST) {
|
||||||
// Don't send user for token auth
|
// Don't send user for token auth
|
||||||
out.writeBoolean(false);
|
out.writeBoolean(false);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//Send both effective user and real user for simple auth
|
//Send both effective user and real user for simple auth
|
||||||
out.writeBoolean(true);
|
out.writeBoolean(true);
|
||||||
|
@ -85,6 +85,7 @@
|
|||||||
*/
|
*/
|
||||||
public abstract class Server {
|
public abstract class Server {
|
||||||
private final boolean authorize;
|
private final boolean authorize;
|
||||||
|
private boolean isSecurityEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first four bytes of Hadoop RPC connections
|
* The first four bytes of Hadoop RPC connections
|
||||||
@ -746,6 +747,7 @@ private class Connection {
|
|||||||
SaslServer saslServer;
|
SaslServer saslServer;
|
||||||
private AuthMethod authMethod;
|
private AuthMethod authMethod;
|
||||||
private boolean saslContextEstablished;
|
private boolean saslContextEstablished;
|
||||||
|
private boolean skipInitialSaslHandshake;
|
||||||
private ByteBuffer rpcHeaderBuffer;
|
private ByteBuffer rpcHeaderBuffer;
|
||||||
private ByteBuffer unwrappedData;
|
private ByteBuffer unwrappedData;
|
||||||
private ByteBuffer unwrappedDataLengthBuffer;
|
private ByteBuffer unwrappedDataLengthBuffer;
|
||||||
@ -929,6 +931,15 @@ private void disposeSasl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void askClientToUseSimpleAuth() throws IOException {
|
||||||
|
saslCall.connection = this;
|
||||||
|
saslResponse.reset();
|
||||||
|
DataOutputStream out = new DataOutputStream(saslResponse);
|
||||||
|
out.writeInt(SaslRpcServer.SWITCH_TO_SIMPLE_AUTH);
|
||||||
|
saslCall.setResponse(ByteBuffer.wrap(saslResponse.toByteArray()));
|
||||||
|
responder.doRespond(saslCall);
|
||||||
|
}
|
||||||
|
|
||||||
public int readAndProcess() throws IOException, InterruptedException {
|
public int readAndProcess() throws IOException, InterruptedException {
|
||||||
while (true) {
|
while (true) {
|
||||||
/* Read at most one RPC. If the header is not read completely yet
|
/* Read at most one RPC. If the header is not read completely yet
|
||||||
@ -957,13 +968,16 @@ public int readAndProcess() throws IOException, InterruptedException {
|
|||||||
if (authMethod == null) {
|
if (authMethod == null) {
|
||||||
throw new IOException("Unable to read authentication method");
|
throw new IOException("Unable to read authentication method");
|
||||||
}
|
}
|
||||||
if (UserGroupInformation.isSecurityEnabled()
|
if (isSecurityEnabled && authMethod == AuthMethod.SIMPLE) {
|
||||||
&& authMethod == AuthMethod.SIMPLE) {
|
|
||||||
throw new IOException("Authentication is required");
|
throw new IOException("Authentication is required");
|
||||||
}
|
}
|
||||||
if (!UserGroupInformation.isSecurityEnabled()
|
if (!isSecurityEnabled && authMethod != AuthMethod.SIMPLE) {
|
||||||
&& authMethod != AuthMethod.SIMPLE) {
|
askClientToUseSimpleAuth();
|
||||||
throw new IOException("Authentication is not supported");
|
authMethod = AuthMethod.SIMPLE;
|
||||||
|
// client has already sent the initial Sasl message and we
|
||||||
|
// should ignore it. Both client and server should fall back
|
||||||
|
// to simple auth from now on.
|
||||||
|
skipInitialSaslHandshake = true;
|
||||||
}
|
}
|
||||||
if (authMethod != AuthMethod.SIMPLE) {
|
if (authMethod != AuthMethod.SIMPLE) {
|
||||||
useSasl = true;
|
useSasl = true;
|
||||||
@ -1000,6 +1014,11 @@ public int readAndProcess() throws IOException, InterruptedException {
|
|||||||
if (data.remaining() == 0) {
|
if (data.remaining() == 0) {
|
||||||
dataLengthBuffer.clear();
|
dataLengthBuffer.clear();
|
||||||
data.flip();
|
data.flip();
|
||||||
|
if (skipInitialSaslHandshake) {
|
||||||
|
data = null;
|
||||||
|
skipInitialSaslHandshake = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
boolean isHeaderRead = headerRead;
|
boolean isHeaderRead = headerRead;
|
||||||
if (useSasl) {
|
if (useSasl) {
|
||||||
saslReadAndProcess(data.array());
|
saslReadAndProcess(data.array());
|
||||||
@ -1278,6 +1297,7 @@ protected Server(String bindAddress, int port,
|
|||||||
this.authorize =
|
this.authorize =
|
||||||
conf.getBoolean(ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG,
|
conf.getBoolean(ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG,
|
||||||
false);
|
false);
|
||||||
|
this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled();
|
||||||
|
|
||||||
// Start the listener here and let it bind to the port
|
// Start the listener here and let it bind to the port
|
||||||
listener = new Listener();
|
listener = new Listener();
|
||||||
@ -1355,6 +1375,11 @@ Configuration getConf() {
|
|||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** for unit testing only, should be called before server is started */
|
||||||
|
void disableSecurity() {
|
||||||
|
this.isSecurityEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets the socket buffer size used for responding to RPCs */
|
/** Sets the socket buffer size used for responding to RPCs */
|
||||||
public void setSocketSendBufSize(int size) { this.socketSendBufferSize = size; }
|
public void setSocketSendBufSize(int size) { this.socketSendBufferSize = size; }
|
||||||
|
|
||||||
|
@ -107,9 +107,11 @@ public SaslRpcClient(AuthMethod method,
|
|||||||
* InputStream to use
|
* InputStream to use
|
||||||
* @param outS
|
* @param outS
|
||||||
* OutputStream to use
|
* OutputStream to use
|
||||||
|
* @return true if connection is set up, or false if needs to switch
|
||||||
|
* to simple Auth.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void saslConnect(InputStream inS, OutputStream outS)
|
public boolean saslConnect(InputStream inS, OutputStream outS)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
DataInputStream inStream = new DataInputStream(new BufferedInputStream(inS));
|
DataInputStream inStream = new DataInputStream(new BufferedInputStream(inS));
|
||||||
DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(
|
DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(
|
||||||
@ -128,7 +130,14 @@ public void saslConnect(InputStream inS, OutputStream outS)
|
|||||||
+ " from initSASLContext.");
|
+ " from initSASLContext.");
|
||||||
}
|
}
|
||||||
if (!saslClient.isComplete()) {
|
if (!saslClient.isComplete()) {
|
||||||
saslToken = new byte[inStream.readInt()];
|
int len = inStream.readInt();
|
||||||
|
if (len == SaslRpcServer.SWITCH_TO_SIMPLE_AUTH) {
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Server asks us to fall back to simple auth.");
|
||||||
|
saslClient.dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
saslToken = new byte[len];
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Will read input token of size " + saslToken.length
|
LOG.debug("Will read input token of size " + saslToken.length
|
||||||
+ " for processing by initSASLContext");
|
+ " for processing by initSASLContext");
|
||||||
@ -157,8 +166,13 @@ public void saslConnect(InputStream inS, OutputStream outS)
|
|||||||
LOG.debug("SASL client context established. Negotiated QoP: "
|
LOG.debug("SASL client context established. Negotiated QoP: "
|
||||||
+ saslClient.getNegotiatedProperty(Sasl.QOP));
|
+ saslClient.getNegotiatedProperty(Sasl.QOP));
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
saslClient.dispose();
|
saslClient.dispose();
|
||||||
|
} catch (SaslException ignored) {
|
||||||
|
// ignore further exceptions during cleanup
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ public class SaslRpcServer {
|
|||||||
// Request mutual authentication
|
// Request mutual authentication
|
||||||
SASL_PROPS.put(Sasl.SERVER_AUTH, "true");
|
SASL_PROPS.put(Sasl.SERVER_AUTH, "true");
|
||||||
}
|
}
|
||||||
|
public static final int SWITCH_TO_SIMPLE_AUTH = -88;
|
||||||
|
|
||||||
static String encodeIdentifier(byte[] identifier) {
|
static String encodeIdentifier(byte[] identifier) {
|
||||||
return new String(Base64.encodeBase64(identifier));
|
return new String(Base64.encodeBase64(identifier));
|
||||||
|
@ -75,20 +75,14 @@ public static class TestTokenIdentifier extends TokenIdentifier {
|
|||||||
final static Text KIND_NAME = new Text("test.token");
|
final static Text KIND_NAME = new Text("test.token");
|
||||||
|
|
||||||
public TestTokenIdentifier() {
|
public TestTokenIdentifier() {
|
||||||
this.tokenid = new Text();
|
this(new Text(), new Text());
|
||||||
this.realUser = new Text();
|
|
||||||
}
|
}
|
||||||
public TestTokenIdentifier(Text tokenid) {
|
public TestTokenIdentifier(Text tokenid) {
|
||||||
this.tokenid = tokenid;
|
this(tokenid, new Text());
|
||||||
this.realUser = new Text();
|
|
||||||
}
|
}
|
||||||
public TestTokenIdentifier(Text tokenid, Text realUser) {
|
public TestTokenIdentifier(Text tokenid, Text realUser) {
|
||||||
this.tokenid = tokenid;
|
this.tokenid = tokenid == null ? new Text() : tokenid;
|
||||||
if (realUser == null) {
|
this.realUser = realUser == null ? new Text() : realUser;
|
||||||
this.realUser = new Text();
|
|
||||||
} else {
|
|
||||||
this.realUser = realUser;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public Text getKind() {
|
public Text getKind() {
|
||||||
@ -96,7 +90,7 @@ public Text getKind() {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public UserGroupInformation getUser() {
|
public UserGroupInformation getUser() {
|
||||||
if ((realUser == null) || ("".equals(realUser.toString()))) {
|
if ("".equals(realUser.toString())) {
|
||||||
return UserGroupInformation.createRemoteUser(tokenid.toString());
|
return UserGroupInformation.createRemoteUser(tokenid.toString());
|
||||||
} else {
|
} else {
|
||||||
UserGroupInformation realUgi = UserGroupInformation
|
UserGroupInformation realUgi = UserGroupInformation
|
||||||
@ -114,11 +108,9 @@ public void readFields(DataInput in) throws IOException {
|
|||||||
@Override
|
@Override
|
||||||
public void write(DataOutput out) throws IOException {
|
public void write(DataOutput out) throws IOException {
|
||||||
tokenid.write(out);
|
tokenid.write(out);
|
||||||
if (realUser != null) {
|
|
||||||
realUser.write(out);
|
realUser.write(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestTokenSecretManager extends
|
public static class TestTokenSecretManager extends
|
||||||
SecretManager<TestTokenIdentifier> {
|
SecretManager<TestTokenIdentifier> {
|
||||||
@ -170,6 +162,20 @@ public void testDigestRpc() throws Exception {
|
|||||||
final Server server = RPC.getServer(TestSaslProtocol.class,
|
final Server server = RPC.getServer(TestSaslProtocol.class,
|
||||||
new TestSaslImpl(), ADDRESS, 0, 5, true, conf, sm);
|
new TestSaslImpl(), ADDRESS, 0, 5, true, conf, sm);
|
||||||
|
|
||||||
|
doDigestRpc(server, sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecureToInsecureRpc() throws Exception {
|
||||||
|
Server server = RPC.getServer(TestSaslProtocol.class,
|
||||||
|
new TestSaslImpl(), ADDRESS, 0, 5, true, conf, null);
|
||||||
|
server.disableSecurity();
|
||||||
|
TestTokenSecretManager sm = new TestTokenSecretManager();
|
||||||
|
doDigestRpc(server, sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDigestRpc(Server server, TestTokenSecretManager sm)
|
||||||
|
throws Exception {
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
final UserGroupInformation current = UserGroupInformation.getCurrentUser();
|
final UserGroupInformation current = UserGroupInformation.getCurrentUser();
|
||||||
|
@ -44,6 +44,14 @@
|
|||||||
<Field name="out" />
|
<Field name="out" />
|
||||||
<Bug pattern="IS2_INCONSISTENT_SYNC" />
|
<Bug pattern="IS2_INCONSISTENT_SYNC" />
|
||||||
</Match>
|
</Match>
|
||||||
|
<!--
|
||||||
|
Further SaslException should be ignored during cleanup and
|
||||||
|
original exception should be re-thrown.
|
||||||
|
-->
|
||||||
|
<Match>
|
||||||
|
<Class name="org.apache.hadoop.security.SaslRpcClient" />
|
||||||
|
<Bug pattern="DE_MIGHT_IGNORE" />
|
||||||
|
</Match>
|
||||||
<!--
|
<!--
|
||||||
Ignore Cross Scripting Vulnerabilities
|
Ignore Cross Scripting Vulnerabilities
|
||||||
-->
|
-->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user