HADOOP-9012. IPC Client sends wrong connection context (daryn via bobby)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1406184 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Joseph Evans 2012-11-06 15:37:14 +00:00
parent dca8dd7a20
commit 5605b54010
3 changed files with 129 additions and 104 deletions

View File

@ -395,6 +395,8 @@ Release 2.0.3-alpha - Unreleased
HADOOP-8713. TestRPCCompatibility fails intermittently with JDK7 HADOOP-8713. TestRPCCompatibility fails intermittently with JDK7
(Trevor Robinson via tgraves) (Trevor Robinson via tgraves)
HADOOP-9012. IPC Client sends wrong connection context (daryn via bobby)
Release 2.0.2-alpha - 2012-09-07 Release 2.0.2-alpha - 2012-09-07
INCOMPATIBLE CHANGES INCOMPATIBLE CHANGES

View File

@ -223,7 +223,6 @@ public class Client {
private class Connection extends Thread { private class Connection extends Thread {
private InetSocketAddress server; // server ip:port private InetSocketAddress server; // server ip:port
private String serverPrincipal; // server's krb5 principal name private String serverPrincipal; // server's krb5 principal name
private IpcConnectionContextProto connectionContext; // connection context
private final ConnectionId remoteId; // connection id private final ConnectionId remoteId; // connection id
private AuthMethod authMethod; // authentication method private AuthMethod authMethod; // authentication method
private Token<? extends TokenIdentifier> token; private Token<? extends TokenIdentifier> token;
@ -304,9 +303,6 @@ public class Client {
authMethod = AuthMethod.SIMPLE; authMethod = AuthMethod.SIMPLE;
} }
connectionContext = ProtoUtil.makeIpcConnectionContext(
RPC.getProtocolName(protocol), ticket, authMethod);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Use " + authMethod + " authentication for protocol " LOG.debug("Use " + authMethod + " authentication for protocol "
+ protocol.getSimpleName()); + protocol.getSimpleName());
@ -607,11 +603,6 @@ public class Client {
} else { } else {
// fall back to simple auth because server told us so. // fall back to simple auth because server told us so.
authMethod = AuthMethod.SIMPLE; authMethod = AuthMethod.SIMPLE;
// remake the connectionContext
connectionContext = ProtoUtil.makeIpcConnectionContext(
connectionContext.getProtocol(),
ProtoUtil.getUgi(connectionContext.getUserInfo()),
authMethod);
} }
} }
@ -622,7 +613,7 @@ public class Client {
this.in = new DataInputStream(new BufferedInputStream(inStream)); this.in = new DataInputStream(new BufferedInputStream(inStream));
} }
this.out = new DataOutputStream(new BufferedOutputStream(outStream)); this.out = new DataOutputStream(new BufferedOutputStream(outStream));
writeConnectionContext(); writeConnectionContext(remoteId, authMethod);
// update last activity time // update last activity time
touch(); touch();
@ -744,10 +735,15 @@ public class Client {
/* Write the connection context header for each connection /* Write the connection context header for each connection
* Out is not synchronized because only the first thread does this. * Out is not synchronized because only the first thread does this.
*/ */
private void writeConnectionContext() throws IOException { private void writeConnectionContext(ConnectionId remoteId,
AuthMethod authMethod)
throws IOException {
// Write out the ConnectionHeader // Write out the ConnectionHeader
DataOutputBuffer buf = new DataOutputBuffer(); DataOutputBuffer buf = new DataOutputBuffer();
connectionContext.writeTo(buf); ProtoUtil.makeIpcConnectionContext(
RPC.getProtocolName(remoteId.getProtocol()),
remoteId.getTicket(),
authMethod).writeTo(buf);
// Write out the payload length // Write out the payload length
int bufLen = buf.getLength(); int bufLen = buf.getLength();

View File

@ -29,6 +29,7 @@ import java.net.InetSocketAddress;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import javax.security.sasl.Sasl; import javax.security.sasl.Sasl;
@ -42,7 +43,6 @@ import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Text;
import org.apache.hadoop.ipc.Client.ConnectionId; import org.apache.hadoop.ipc.Client.ConnectionId;
import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.KerberosInfo; import org.apache.hadoop.security.KerberosInfo;
import org.apache.hadoop.security.SaslInputStream; import org.apache.hadoop.security.SaslInputStream;
import org.apache.hadoop.security.SaslRpcClient; import org.apache.hadoop.security.SaslRpcClient;
@ -59,7 +59,7 @@ import org.apache.hadoop.security.token.TokenInfo;
import org.apache.hadoop.security.token.TokenSelector; import org.apache.hadoop.security.token.TokenSelector;
import org.apache.hadoop.security.token.SecretManager.InvalidToken; import org.apache.hadoop.security.token.SecretManager.InvalidToken;
import org.apache.log4j.Level; import org.apache.log4j.Level;
import org.junit.BeforeClass; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
/** Unit tests for using Sasl over RPC. */ /** Unit tests for using Sasl over RPC. */
@ -76,8 +76,9 @@ public class TestSaslRPC {
static final String SERVER_PRINCIPAL_2 = "p2/foo@BAR"; static final String SERVER_PRINCIPAL_2 = "p2/foo@BAR";
private static Configuration conf; private static Configuration conf;
@BeforeClass
public static void setup() { @Before
public void setup() {
conf = new Configuration(); conf = new Configuration();
SecurityUtil.setAuthenticationMethod(KERBEROS, conf); SecurityUtil.setAuthenticationMethod(KERBEROS, conf);
UserGroupInformation.setConfiguration(conf); UserGroupInformation.setConfiguration(conf);
@ -187,6 +188,7 @@ public class TestSaslRPC {
@TokenInfo(TestTokenSelector.class) @TokenInfo(TestTokenSelector.class)
public interface TestSaslProtocol extends TestRPC.TestProtocol { public interface TestSaslProtocol extends TestRPC.TestProtocol {
public AuthenticationMethod getAuthMethod() throws IOException; public AuthenticationMethod getAuthMethod() throws IOException;
public String getAuthUser() throws IOException;
} }
public static class TestSaslImpl extends TestRPC.TestImpl implements public static class TestSaslImpl extends TestRPC.TestImpl implements
@ -195,6 +197,10 @@ public class TestSaslRPC {
public AuthenticationMethod getAuthMethod() throws IOException { public AuthenticationMethod getAuthMethod() throws IOException {
return UserGroupInformation.getCurrentUser().getAuthenticationMethod(); return UserGroupInformation.getCurrentUser().getAuthenticationMethod();
} }
@Override
public String getAuthUser() throws IOException {
return UserGroupInformation.getCurrentUser().getUserName();
}
} }
public static class CustomSecurityInfo extends SecurityInfo { public static class CustomSecurityInfo extends SecurityInfo {
@ -261,6 +267,7 @@ public class TestSaslRPC {
@Test @Test
public void testSecureToInsecureRpc() throws Exception { public void testSecureToInsecureRpc() throws Exception {
SecurityUtil.setAuthenticationMethod(AuthenticationMethod.SIMPLE, conf);
Server server = new RPC.Builder(conf).setProtocol(TestSaslProtocol.class) Server server = new RPC.Builder(conf).setProtocol(TestSaslProtocol.class)
.setInstance(new TestSaslImpl()).setBindAddress(ADDRESS).setPort(0) .setInstance(new TestSaslImpl()).setBindAddress(ADDRESS).setPort(0)
.setNumHandlers(5).setVerbose(true).build(); .setNumHandlers(5).setVerbose(true).build();
@ -448,129 +455,135 @@ public class TestSaslRPC {
System.out.println("Test is successful."); System.out.println("Test is successful.");
} }
// insecure -> insecure private static Pattern BadToken =
Pattern.compile(".*DIGEST-MD5: digest response format violation.*");
private static Pattern KrbFailed =
Pattern.compile(".*Failed on local exception:.* " +
"Failed to specify server's Kerberos principal name.*");
private static Pattern Denied =
Pattern.compile(".*Authorization .* is enabled .*");
/*
* simple server
*/
@Test @Test
public void testInsecureClientInsecureServer() throws Exception { public void testSimpleServer() throws Exception {
assertEquals(AuthenticationMethod.SIMPLE, assertAuthEquals(SIMPLE, getAuthMethod(SIMPLE, SIMPLE));
getAuthMethod(false, false, false)); // SASL methods are reverted to SIMPLE, but test setup fails
assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, SIMPLE));
} }
@Test @Test
public void testInsecureClientInsecureServerWithToken() throws Exception { public void testSimpleServerWithTokens() throws Exception {
assertEquals(AuthenticationMethod.TOKEN, // Tokens are ignored because client is reverted to simple
getAuthMethod(false, false, true)); assertAuthEquals(SIMPLE, getAuthMethod(SIMPLE, SIMPLE, true));
assertAuthEquals(SIMPLE, getAuthMethod(KERBEROS, SIMPLE, true));
} }
// insecure -> secure
@Test @Test
public void testInsecureClientSecureServer() throws Exception { public void testSimpleServerWithInvalidTokens() throws Exception {
RemoteException e = null; // Tokens are ignored because client is reverted to simple
assertAuthEquals(SIMPLE, getAuthMethod(SIMPLE, SIMPLE, false));
assertAuthEquals(SIMPLE, getAuthMethod(KERBEROS, SIMPLE, false));
}
/*
* kerberos server
*/
@Test
public void testKerberosServer() throws Exception {
assertAuthEquals(Denied, getAuthMethod(SIMPLE, KERBEROS));
assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, KERBEROS));
}
@Test
public void testKerberosServerWithTokens() throws Exception {
// can use tokens regardless of auth
assertAuthEquals(TOKEN, getAuthMethod(SIMPLE, KERBEROS, true));
assertAuthEquals(TOKEN, getAuthMethod(KERBEROS, KERBEROS, true));
}
@Test
public void testKerberosServerWithInvalidTokens() throws Exception {
assertAuthEquals(BadToken, getAuthMethod(SIMPLE, KERBEROS, false));
assertAuthEquals(BadToken, getAuthMethod(KERBEROS, KERBEROS, false));
}
// test helpers
private String getAuthMethod(
final AuthenticationMethod clientAuth,
final AuthenticationMethod serverAuth) throws Exception {
try { try {
getAuthMethod(false, true, false); return internalGetAuthMethod(clientAuth, serverAuth, false, false);
} catch (RemoteException re) { } catch (Exception e) {
e = re; return e.toString();
}
assertNotNull(e);
assertEquals(AccessControlException.class.getName(), e.getClassName());
}
@Test
public void testInsecureClientSecureServerWithToken() throws Exception {
assertEquals(AuthenticationMethod.TOKEN,
getAuthMethod(false, true, true));
}
// secure -> secure
@Test
public void testSecureClientSecureServer() throws Exception {
/* Should be this when multiple secure auths are supported and we can
* dummy one out:
* assertEquals(AuthenticationMethod.SECURE_AUTH_METHOD,
* getAuthMethod(true, true, false));
*/
try {
getAuthMethod(true, true, false);
} catch (IOException ioe) {
// can't actually test kerberos w/o kerberos...
String expectedError = "Failed to specify server's Kerberos principal";
String actualError = ioe.getMessage();
assertTrue("["+actualError+"] doesn't start with ["+expectedError+"]",
actualError.contains(expectedError));
} }
} }
@Test private String getAuthMethod(
public void testSecureClientSecureServerWithToken() throws Exception { final AuthenticationMethod clientAuth,
assertEquals(AuthenticationMethod.TOKEN, final AuthenticationMethod serverAuth,
getAuthMethod(true, true, true)); final boolean useValidToken) throws Exception {
}
// secure -> insecure
@Test
public void testSecureClientInsecureServerWithToken() throws Exception {
assertEquals(AuthenticationMethod.TOKEN,
getAuthMethod(true, false, true));
}
@Test
public void testSecureClientInsecureServer() throws Exception {
/* Should be this when multiple secure auths are supported and we can
* dummy one out:
* assertEquals(AuthenticationMethod.SIMPLE
* getAuthMethod(true, false, false));
*/
try { try {
getAuthMethod(true, false, false); return internalGetAuthMethod(clientAuth, serverAuth, true, useValidToken);
} catch (IOException ioe) { } catch (Exception e) {
// can't actually test kerberos w/o kerberos... return e.toString();
String expectedError = "Failed to specify server's Kerberos principal";
String actualError = ioe.getMessage();
assertTrue("["+actualError+"] doesn't start with ["+expectedError+"]",
actualError.contains(expectedError));
} }
} }
private String internalGetAuthMethod(
final AuthenticationMethod clientAuth,
final AuthenticationMethod serverAuth,
final boolean useToken,
final boolean useValidToken) throws Exception {
private AuthenticationMethod getAuthMethod(final boolean isSecureClient,
final boolean isSecureServer,
final boolean useToken
) throws Exception {
Configuration serverConf = new Configuration(conf); Configuration serverConf = new Configuration(conf);
SecurityUtil.setAuthenticationMethod( SecurityUtil.setAuthenticationMethod(serverAuth, serverConf);
isSecureServer ? KERBEROS : SIMPLE, serverConf);
UserGroupInformation.setConfiguration(serverConf); UserGroupInformation.setConfiguration(serverConf);
TestTokenSecretManager sm = new TestTokenSecretManager(); TestTokenSecretManager sm = new TestTokenSecretManager();
Server server = new RPC.Builder(serverConf).setProtocol(TestSaslProtocol.class) Server server = new RPC.Builder(serverConf).setProtocol(TestSaslProtocol.class)
.setInstance(new TestSaslImpl()).setBindAddress(ADDRESS).setPort(0) .setInstance(new TestSaslImpl()).setBindAddress(ADDRESS).setPort(0)
.setNumHandlers(5).setVerbose(true).setSecretManager(sm).build(); .setNumHandlers(5).setVerbose(true)
.setSecretManager((serverAuth != SIMPLE) ? sm : null)
.build();
server.start(); server.start();
final UserGroupInformation current = UserGroupInformation.getCurrentUser(); final UserGroupInformation clientUgi =
UserGroupInformation.createRemoteUser(
UserGroupInformation.getCurrentUser().getUserName()+"-CLIENT");
final InetSocketAddress addr = NetUtils.getConnectAddress(server); final InetSocketAddress addr = NetUtils.getConnectAddress(server);
if (useToken) { if (useToken) {
TestTokenIdentifier tokenId = new TestTokenIdentifier( TestTokenIdentifier tokenId = new TestTokenIdentifier(
new Text(current.getUserName())); new Text(clientUgi.getUserName()));
Token<TestTokenIdentifier> token = Token<TestTokenIdentifier> token = useValidToken
new Token<TestTokenIdentifier>(tokenId, sm); ? new Token<TestTokenIdentifier>(tokenId, sm)
: new Token<TestTokenIdentifier>(
tokenId.getBytes(), "bad-password!".getBytes(),
tokenId.getKind(), null);
SecurityUtil.setTokenService(token, addr); SecurityUtil.setTokenService(token, addr);
current.addToken(token); clientUgi.addToken(token);
} }
final Configuration clientConf = new Configuration(conf); final Configuration clientConf = new Configuration(conf);
SecurityUtil.setAuthenticationMethod( SecurityUtil.setAuthenticationMethod(clientAuth, clientConf);
isSecureClient ? KERBEROS : SIMPLE, clientConf);
UserGroupInformation.setConfiguration(clientConf); UserGroupInformation.setConfiguration(clientConf);
try { try {
return current.doAs(new PrivilegedExceptionAction<AuthenticationMethod>() { return clientUgi.doAs(new PrivilegedExceptionAction<String>() {
@Override @Override
public AuthenticationMethod run() throws IOException { public String run() throws IOException {
TestSaslProtocol proxy = null; TestSaslProtocol proxy = null;
try { try {
proxy = (TestSaslProtocol) RPC.getProxy(TestSaslProtocol.class, proxy = (TestSaslProtocol) RPC.getProxy(TestSaslProtocol.class,
TestSaslProtocol.versionID, addr, clientConf); TestSaslProtocol.versionID, addr, clientConf);
return proxy.getAuthMethod();
// make sure the other side thinks we are who we said we are!!!
assertEquals(clientUgi.getUserName(), proxy.getAuthUser());
return proxy.getAuthMethod().toString();
} finally { } finally {
if (proxy != null) { if (proxy != null) {
RPC.stopProxy(proxy); RPC.stopProxy(proxy);
@ -583,6 +596,21 @@ public class TestSaslRPC {
} }
} }
private static void assertAuthEquals(AuthenticationMethod expect,
String actual) {
assertEquals(expect.toString(), actual);
}
private static void assertAuthEquals(Pattern expect,
String actual) {
// this allows us to see the regexp and the value it didn't match
if (!expect.matcher(actual).matches()) {
assertEquals(expect, actual); // it failed
} else {
assertTrue(true); // it matched
}
}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
System.out.println("Testing Kerberos authentication over RPC"); System.out.println("Testing Kerberos authentication over RPC");
if (args.length != 2) { if (args.length != 2) {
@ -595,5 +623,4 @@ public class TestSaslRPC {
String keytab = args[1]; String keytab = args[1];
testKerberosRpc(principal, keytab); testKerberosRpc(principal, keytab);
} }
} }