From 5605b54010b67785085192629d9a191e0c79bd90 Mon Sep 17 00:00:00 2001 From: Robert Joseph Evans Date: Tue, 6 Nov 2012 15:37:14 +0000 Subject: [PATCH] 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 --- .../hadoop-common/CHANGES.txt | 2 + .../java/org/apache/hadoop/ipc/Client.java | 20 +- .../org/apache/hadoop/ipc/TestSaslRPC.java | 211 ++++++++++-------- 3 files changed, 129 insertions(+), 104 deletions(-) diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 5cac902f0e..e349cb4526 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -395,6 +395,8 @@ Release 2.0.3-alpha - Unreleased HADOOP-8713. TestRPCCompatibility fails intermittently with JDK7 (Trevor Robinson via tgraves) + HADOOP-9012. IPC Client sends wrong connection context (daryn via bobby) + Release 2.0.2-alpha - 2012-09-07 INCOMPATIBLE CHANGES diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java index aea73b4c38..1af14f941c 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Client.java @@ -223,7 +223,6 @@ public synchronized Writable getRpcResult() { private class Connection extends Thread { private InetSocketAddress server; // server ip:port private String serverPrincipal; // server's krb5 principal name - private IpcConnectionContextProto connectionContext; // connection context private final ConnectionId remoteId; // connection id private AuthMethod authMethod; // authentication method private Token token; @@ -304,9 +303,6 @@ public Connection(ConnectionId remoteId) throws IOException { authMethod = AuthMethod.SIMPLE; } - connectionContext = ProtoUtil.makeIpcConnectionContext( - RPC.getProtocolName(protocol), ticket, authMethod); - if (LOG.isDebugEnabled()) LOG.debug("Use " + authMethod + " authentication for protocol " + protocol.getSimpleName()); @@ -607,11 +603,6 @@ public Boolean run() throws IOException { } else { // fall back to simple auth because server told us so. authMethod = AuthMethod.SIMPLE; - // remake the connectionContext - connectionContext = ProtoUtil.makeIpcConnectionContext( - connectionContext.getProtocol(), - ProtoUtil.getUgi(connectionContext.getUserInfo()), - authMethod); } } @@ -622,7 +613,7 @@ public Boolean run() throws IOException { this.in = new DataInputStream(new BufferedInputStream(inStream)); } this.out = new DataOutputStream(new BufferedOutputStream(outStream)); - writeConnectionContext(); + writeConnectionContext(remoteId, authMethod); // update last activity time touch(); @@ -744,10 +735,15 @@ private void writeConnectionHeader(OutputStream outStream) /* Write the connection context header for each connection * 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 DataOutputBuffer buf = new DataOutputBuffer(); - connectionContext.writeTo(buf); + ProtoUtil.makeIpcConnectionContext( + RPC.getProtocolName(remoteId.getProtocol()), + remoteId.getTicket(), + authMethod).writeTo(buf); // Write out the payload length int bufLen = buf.getLength(); diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java index 6a4684e788..7abd6e9dac 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java @@ -29,6 +29,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Collection; import java.util.Set; +import java.util.regex.Pattern; import javax.security.sasl.Sasl; @@ -42,7 +43,6 @@ import org.apache.hadoop.io.Text; import org.apache.hadoop.ipc.Client.ConnectionId; import org.apache.hadoop.net.NetUtils; -import org.apache.hadoop.security.AccessControlException; import org.apache.hadoop.security.KerberosInfo; import org.apache.hadoop.security.SaslInputStream; import org.apache.hadoop.security.SaslRpcClient; @@ -59,7 +59,7 @@ import org.apache.hadoop.security.token.TokenSelector; import org.apache.hadoop.security.token.SecretManager.InvalidToken; import org.apache.log4j.Level; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; /** Unit tests for using Sasl over RPC. */ @@ -76,8 +76,9 @@ public class TestSaslRPC { static final String SERVER_PRINCIPAL_2 = "p2/foo@BAR"; private static Configuration conf; - @BeforeClass - public static void setup() { + + @Before + public void setup() { conf = new Configuration(); SecurityUtil.setAuthenticationMethod(KERBEROS, conf); UserGroupInformation.setConfiguration(conf); @@ -187,6 +188,7 @@ public Token selectToken(Text service, @TokenInfo(TestTokenSelector.class) public interface TestSaslProtocol extends TestRPC.TestProtocol { public AuthenticationMethod getAuthMethod() throws IOException; + public String getAuthUser() throws IOException; } public static class TestSaslImpl extends TestRPC.TestImpl implements @@ -195,6 +197,10 @@ public static class TestSaslImpl extends TestRPC.TestImpl implements public AuthenticationMethod getAuthMethod() throws IOException { return UserGroupInformation.getCurrentUser().getAuthenticationMethod(); } + @Override + public String getAuthUser() throws IOException { + return UserGroupInformation.getCurrentUser().getUserName(); + } } public static class CustomSecurityInfo extends SecurityInfo { @@ -261,6 +267,7 @@ public void testDigestRpcWithoutAnnotation() throws Exception { @Test public void testSecureToInsecureRpc() throws Exception { + SecurityUtil.setAuthenticationMethod(AuthenticationMethod.SIMPLE, conf); Server server = new RPC.Builder(conf).setProtocol(TestSaslProtocol.class) .setInstance(new TestSaslImpl()).setBindAddress(ADDRESS).setPort(0) .setNumHandlers(5).setVerbose(true).build(); @@ -448,129 +455,135 @@ static void testKerberosRpc(String principal, String keytab) throws Exception { 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 - public void testInsecureClientInsecureServer() throws Exception { - assertEquals(AuthenticationMethod.SIMPLE, - getAuthMethod(false, false, false)); + public void testSimpleServer() throws Exception { + assertAuthEquals(SIMPLE, getAuthMethod(SIMPLE, SIMPLE)); + // SASL methods are reverted to SIMPLE, but test setup fails + assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, SIMPLE)); } @Test - public void testInsecureClientInsecureServerWithToken() throws Exception { - assertEquals(AuthenticationMethod.TOKEN, - getAuthMethod(false, false, true)); + public void testSimpleServerWithTokens() throws Exception { + // Tokens are ignored because client is reverted to simple + assertAuthEquals(SIMPLE, getAuthMethod(SIMPLE, SIMPLE, true)); + assertAuthEquals(SIMPLE, getAuthMethod(KERBEROS, SIMPLE, true)); + } + + @Test + public void testSimpleServerWithInvalidTokens() throws Exception { + // 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)); } - // insecure -> secure @Test - public void testInsecureClientSecureServer() throws Exception { - RemoteException e = null; + 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 { - getAuthMethod(false, true, false); - } catch (RemoteException re) { - e = re; - } - 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)); + return internalGetAuthMethod(clientAuth, serverAuth, false, false); + } catch (Exception e) { + return e.toString(); } } - @Test - public void testSecureClientSecureServerWithToken() throws Exception { - assertEquals(AuthenticationMethod.TOKEN, - getAuthMethod(true, true, true)); - } - - // 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)); - */ + private String getAuthMethod( + final AuthenticationMethod clientAuth, + final AuthenticationMethod serverAuth, + final boolean useValidToken) throws Exception { try { - getAuthMethod(true, false, 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)); + return internalGetAuthMethod(clientAuth, serverAuth, true, useValidToken); + } catch (Exception e) { + return e.toString(); } } - - - private AuthenticationMethod getAuthMethod(final boolean isSecureClient, - final boolean isSecureServer, - final boolean useToken - - ) throws Exception { + + private String internalGetAuthMethod( + final AuthenticationMethod clientAuth, + final AuthenticationMethod serverAuth, + final boolean useToken, + final boolean useValidToken) throws Exception { + Configuration serverConf = new Configuration(conf); - SecurityUtil.setAuthenticationMethod( - isSecureServer ? KERBEROS : SIMPLE, serverConf); + SecurityUtil.setAuthenticationMethod(serverAuth, serverConf); UserGroupInformation.setConfiguration(serverConf); TestTokenSecretManager sm = new TestTokenSecretManager(); Server server = new RPC.Builder(serverConf).setProtocol(TestSaslProtocol.class) .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(); - final UserGroupInformation current = UserGroupInformation.getCurrentUser(); + final UserGroupInformation clientUgi = + UserGroupInformation.createRemoteUser( + UserGroupInformation.getCurrentUser().getUserName()+"-CLIENT"); final InetSocketAddress addr = NetUtils.getConnectAddress(server); if (useToken) { TestTokenIdentifier tokenId = new TestTokenIdentifier( - new Text(current.getUserName())); - Token token = - new Token(tokenId, sm); + new Text(clientUgi.getUserName())); + Token token = useValidToken + ? new Token(tokenId, sm) + : new Token( + tokenId.getBytes(), "bad-password!".getBytes(), + tokenId.getKind(), null); + SecurityUtil.setTokenService(token, addr); - current.addToken(token); + clientUgi.addToken(token); } final Configuration clientConf = new Configuration(conf); - SecurityUtil.setAuthenticationMethod( - isSecureClient ? KERBEROS : SIMPLE, clientConf); + SecurityUtil.setAuthenticationMethod(clientAuth, clientConf); UserGroupInformation.setConfiguration(clientConf); + try { - return current.doAs(new PrivilegedExceptionAction() { + return clientUgi.doAs(new PrivilegedExceptionAction() { @Override - public AuthenticationMethod run() throws IOException { + public String run() throws IOException { TestSaslProtocol proxy = null; try { proxy = (TestSaslProtocol) RPC.getProxy(TestSaslProtocol.class, 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 { if (proxy != null) { RPC.stopProxy(proxy); @@ -582,7 +595,22 @@ public AuthenticationMethod run() throws IOException { server.stop(); } } + + 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 { System.out.println("Testing Kerberos authentication over RPC"); if (args.length != 2) { @@ -595,5 +623,4 @@ public static void main(String[] args) throws Exception { String keytab = args[1]; testKerberosRpc(principal, keytab); } - }