Repository: kudu Updated Branches: refs/heads/master 07e55ed0b -> ed44a20df
java: support TLS_AUTHENTICATION_ONLY This is tested by a new unit test. Unfortunately an integration test is slightly tricky since the daemons run on a different loopback IP than they connect from. Change-Id: I3c0dcca560a4a96a449fc0f5324e404f10247905 Reviewed-on: http://gerrit.cloudera.org:8080/6087 Tested-by: Kudu Jenkins Reviewed-by: Alexey Serbin <[email protected]> Reviewed-by: Dan Burkert <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/73aa3013 Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/73aa3013 Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/73aa3013 Branch: refs/heads/master Commit: 73aa301342200c3f5e06dacdcf59fdb5eb722925 Parents: 07e55ed Author: Todd Lipcon <[email protected]> Authored: Mon Feb 20 14:52:18 2017 -0800 Committer: Todd Lipcon <[email protected]> Committed: Wed Feb 22 21:45:04 2017 +0000 ---------------------------------------------------------------------- .../java/org/apache/kudu/client/Negotiator.java | 40 +++++++++++++++-- .../org/apache/kudu/client/TestNegotiator.java | 47 +++++++++++++++++--- 2 files changed, 77 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/73aa3013/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java b/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java index 856a2bc..1457fcf 100644 --- a/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java +++ b/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java @@ -26,6 +26,8 @@ package org.apache.kudu.client; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.security.PrivilegedExceptionAction; import java.security.cert.Certificate; import java.util.List; @@ -45,6 +47,7 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; @@ -157,6 +160,9 @@ public class Negotiator extends SimpleChannelUpstreamHandler { private Certificate peerCert; + @VisibleForTesting + boolean overrideLoopbackForTests; + public Negotiator(String remoteHostname, SecurityContext securityContext) { this.remoteHostname = remoteHostname; this.securityContext = securityContext; @@ -176,8 +182,9 @@ public class Negotiator extends SimpleChannelUpstreamHandler { for (RpcHeader.RpcFeatureFlag flag : SUPPORTED_RPC_FEATURES) { builder.addSupportedFeatures(flag); } - // TODO(todd): if we are on a loopback connection, advertise and support - // TLS_AUTHENTICATION_ONLY. + if (isLoopbackConnection(channel)) { + builder.addSupportedFeatures(RpcFeatureFlag.TLS_AUTHENTICATION_ONLY); + } // Advertise our authentication types. // ---------------------------------- @@ -297,6 +304,25 @@ public class Negotiator extends SimpleChannelUpstreamHandler { } } + /** + * Determine whether the given channel is a loopback connection (i.e. the server + * and client are on the same host). + */ + private boolean isLoopbackConnection(Channel channel) { + if (overrideLoopbackForTests) { + return true; + } + try { + InetAddress local = ((InetSocketAddress)channel.getLocalAddress()).getAddress(); + InetAddress remote = ((InetSocketAddress)channel.getRemoteAddress()).getAddress(); + return local.equals(remote); + } catch (ClassCastException cce) { + // In the off chance that we have some other type of local/remote address, + // we'll just assume it's not loopback. + return false; + } + } + private void chooseAndInitializeSaslMech(NegotiatePB response) throws SaslException { // Gather the set of server-supported mechanisms. Set<String> serverMechs = Sets.newHashSet(); @@ -371,7 +397,7 @@ public class Negotiator extends SimpleChannelUpstreamHandler { private Set<RpcFeatureFlag> getFeatureFlags(NegotiatePB response) { ImmutableSet.Builder<RpcHeader.RpcFeatureFlag> features = ImmutableSet.builder(); for (RpcHeader.RpcFeatureFlag feature : response.getSupportedFeaturesList()) { - if (SUPPORTED_RPC_FEATURES.contains(feature)) { + if (feature != RpcFeatureFlag.UNKNOWN) { features.add(feature); } } @@ -420,6 +446,7 @@ public class Negotiator extends SimpleChannelUpstreamHandler { // Data was sent -- we must continue the handshake process. return; } + // The handshake completed. // Insert the SSL handler into the pipeline so that all following traffic // gets encrypted, and then move on to the SASL portion of negotiation. @@ -436,7 +463,12 @@ public class Negotiator extends SimpleChannelUpstreamHandler { throw Throwables.propagate(e); } - chan.getPipeline().addFirst("tls", handler); + // Don't wrap the TLS socket if we are using TLS for authentication only. + boolean isAuthOnly = serverFeatures.contains(RpcFeatureFlag.TLS_AUTHENTICATION_ONLY) && + isLoopbackConnection(chan); + if (!isAuthOnly) { + chan.getPipeline().addFirst("tls", handler); + } startAuthentication(chan); } http://git-wip-us.apache.org/repos/asf/kudu/blob/73aa3013/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java ---------------------------------------------------------------------- diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java index 6df45de..b4885f3 100644 --- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java +++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java @@ -95,11 +95,11 @@ public class TestNegotiator { AccessController.getContext())); } - private Negotiator startNegotiation() { + private void startNegotiation(boolean fakeLoopback) { Negotiator negotiator = new Negotiator("127.0.0.1", secContext); + negotiator.overrideLoopbackForTests = fakeLoopback; embedder = new DecoderEmbedder<Object>(negotiator); negotiator.sendHello(embedder.getPipeline().getChannel()); - return negotiator; } static CallResponse fakeResponse(ResponseHeader header, Message body) { @@ -157,7 +157,7 @@ public class TestNegotiator { */ @Test public void testNegotiation() { - startNegotiation(); + startNegotiation(false); // Expect client->server: NEGOTIATE. RpcOutboundMessage msg = (RpcOutboundMessage) embedder.poll(); @@ -262,7 +262,7 @@ public class TestNegotiator { @Test public void testTlsNegotiation() throws Exception { - startNegotiation(); + startNegotiation(false); // Expect client->server: NEGOTIATE, TLS included. RpcOutboundMessage msg = (RpcOutboundMessage) embedder.poll(); @@ -295,6 +295,41 @@ public class TestNegotiator { assertEquals(NegotiateStep.SASL_INITIATE, body.getStep()); } + @Test + public void testTlsNegotiationAuthOnly() throws Exception { + startNegotiation(true); + + // Expect client->server: NEGOTIATE, TLS and TLS_AUTHENTICATION_ONLY included. + RpcOutboundMessage msg = (RpcOutboundMessage) embedder.poll(); + NegotiatePB body = (NegotiatePB) msg.getBody(); + assertEquals(NegotiateStep.NEGOTIATE, body.getStep()); + assertTrue(body.getSupportedFeaturesList().contains(RpcFeatureFlag.TLS)); + assertTrue(body.getSupportedFeaturesList().contains( + RpcFeatureFlag.TLS_AUTHENTICATION_ONLY)); + + // Fake a server response with TLS and TLS_AUTHENTICATION_ONLY enabled. + embedder.offer(fakeResponse( + ResponseHeader.newBuilder().setCallId(Negotiator.SASL_CALL_ID).build(), + NegotiatePB.newBuilder() + .addSaslMechanisms(NegotiatePB.SaslMechanism.newBuilder().setMechanism("PLAIN")) + .addSupportedFeatures(RpcFeatureFlag.TLS) + .addSupportedFeatures(RpcFeatureFlag.TLS_AUTHENTICATION_ONLY) + .setStep(NegotiateStep.NEGOTIATE) + .build())); + + // Expect client->server: TLS_HANDSHAKE. + runTlsHandshake(); + + // The pipeline should *not* have an SSL handler as the first handler, + // since we used TLS for authentication only. + assertFalse(embedder.getPipeline().getFirst() instanceof SslHandler); + + // The Negotiator should have sent the SASL_INITIATE at this point. + msg = (RpcOutboundMessage) embedder.poll(); + body = (NegotiatePB) msg.getBody(); + assertEquals(NegotiateStep.SASL_INITIATE, body.getStep()); + } + /** * Test that, if we don't have any trusted certs, we don't expose * token authentication as an option. @@ -302,7 +337,7 @@ public class TestNegotiator { @Test public void testNoTokenAuthWhenNoTrustedCerts() throws Exception { secContext.setAuthenticationToken(SignedTokenPB.getDefaultInstance()); - startNegotiation(); + startNegotiation(false); // Expect client->server: NEGOTIATE, TLS included, Token not included. RpcOutboundMessage msg = (RpcOutboundMessage) embedder.poll(); @@ -321,7 +356,7 @@ public class TestNegotiator { public void testTokenAuthWithTrustedCerts() throws Exception { secContext.trustCertificate(ByteString.copyFromUtf8(CA_CERT_DER)); secContext.setAuthenticationToken(SignedTokenPB.getDefaultInstance()); - startNegotiation(); + startNegotiation(false); // Expect client->server: NEGOTIATE, TLS included, Token included. RpcOutboundMessage msg = (RpcOutboundMessage) embedder.poll();
