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
(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

View File

@ -223,7 +223,6 @@ public class Client {
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<? extends TokenIdentifier> token;
@ -304,9 +303,6 @@ public class Client {
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 class Client {
} 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 class Client {
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 @@ public class Client {
/* 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();

View File

@ -29,6 +29,7 @@ import java.net.InetSocketAddress;
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.fs.CommonConfigurationKeys;
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.TokenInfo;
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 class TestSaslRPC {
@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 class TestSaslRPC {
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 class TestSaslRPC {
@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 @@ public class TestSaslRPC {
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));
}
// insecure -> secure
@Test
public void testInsecureClientSecureServer() throws Exception {
RemoteException e = null;
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));
}
@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 {
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 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);
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<TestTokenIdentifier> token =
new Token<TestTokenIdentifier>(tokenId, sm);
new Text(clientUgi.getUserName()));
Token<TestTokenIdentifier> token = useValidToken
? new Token<TestTokenIdentifier>(tokenId, sm)
: new Token<TestTokenIdentifier>(
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<AuthenticationMethod>() {
return clientUgi.doAs(new PrivilegedExceptionAction<String>() {
@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);
@ -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 {
System.out.println("Testing Kerberos authentication over RPC");
if (args.length != 2) {
@ -595,5 +623,4 @@ public class TestSaslRPC {
String keytab = args[1];
testKerberosRpc(principal, keytab);
}
}