This is an automated email from the ASF dual-hosted git repository. hanm pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push: new d98a692 ZOOKEEPER-3388: Allow client port to support plaintext and encrypted … d98a692 is described below commit d98a692ff4482f1d97774f25a158ca5473c455e0 Author: Brian Nixon <ni...@fb.com> AuthorDate: Tue Jun 4 15:29:24 2019 -0700 ZOOKEEPER-3388: Allow client port to support plaintext and encrypted … …connections simultaneously Author: Brian Nixon <ni...@fb.com> Reviewers: Enrico Olivelli <eolive...@gmail.com>, Michael Han <h...@apache.org> Closes #944 from enixon/client-port-uni --- build.xml | 2 +- pom.xml | 2 +- .../src/main/resources/markdown/zookeeperAdmin.md | 8 +- .../zookeeper/common/SSLContextAndOptions.java | 23 ++- .../java/org/apache/zookeeper/common/X509Util.java | 18 +- .../zookeeper/server/NettyServerCnxnFactory.java | 215 ++++++++++++++++----- .../zookeeper/server/quorum/QuorumPeerConfig.java | 2 +- .../org/apache/zookeeper/test/ClientSSLTest.java | 35 +++- 8 files changed, 244 insertions(+), 61 deletions(-) diff --git a/build.xml b/build.xml index 3ae8cb4..ec504be 100644 --- a/build.xml +++ b/build.xml @@ -37,7 +37,7 @@ xmlns:cs="antlib:com.puppycrawl.tools.checkstyle.ant"> <property name="audience-annotations.version" value="0.5.0" /> - <property name="netty.version" value="4.1.29.Final"/> + <property name="netty.version" value="4.1.36.Final"/> <property name="junit.version" value="4.12"/> <property name="mockito.version" value="2.27.0"/> diff --git a/pom.xml b/pom.xml index 2d2b95a..abcf007 100755 --- a/pom.xml +++ b/pom.xml @@ -277,7 +277,7 @@ <mockito.version>2.27.0</mockito.version> <hamcrest.version>1.3</hamcrest.version> <commons-cli.version>1.2</commons-cli.version> - <netty.version>4.1.29.Final</netty.version> + <netty.version>4.1.36.Final</netty.version> <jetty.version>9.4.17.v20190418</jetty.version> <jackson.version>2.9.9</jackson.version> <json.version>1.1.1</json.version> diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index ae0071c..d9f9703 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -1190,7 +1190,13 @@ encryption/authentication/authorization performed by the service. (Java system properties: **zookeeper.ssl.handshakeDetectionTimeoutMillis** and **zookeeper.ssl.quorum.handshakeDetectionTimeoutMillis**) **New in 3.5.5:** TBD - + +* *client.portUnification*: + (Java system properties: **zookeeper.client.portUnification**) + Specifies that the client port should accept SSL connections + (using the same configuration as the secure client port). + Default: false + <a name="Experimental+Options%2FFeatures"></a> diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/SSLContextAndOptions.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/SSLContextAndOptions.java index 2d60ab8..232ab67 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/SSLContextAndOptions.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/SSLContextAndOptions.java @@ -23,10 +23,16 @@ import java.net.Socket; import java.util.Arrays; import javax.net.ssl.SSLContext; +import java.util.Collections; +import java.util.List; + import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.ssl.SslContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +51,7 @@ public class SSLContextAndOptions { private final X509Util x509Util; private final String[] enabledProtocols; private final String[] cipherSuites; + private final List<String> cipherSuitesAsList; private final X509Util.ClientAuth clientAuth; private final SSLContext sslContext; private final int handshakeDetectionTimeoutMillis; @@ -60,7 +67,9 @@ public class SSLContextAndOptions { this.x509Util = requireNonNull(x509Util); this.sslContext = requireNonNull(sslContext); this.enabledProtocols = getEnabledProtocols(requireNonNull(config), sslContext); - this.cipherSuites = getCipherSuites(config); + String[] ciphers = getCipherSuites(config); + this.cipherSuites = ciphers; + this.cipherSuitesAsList = Collections.unmodifiableList(Arrays.asList(ciphers)); this.clientAuth = getClientAuth(config); this.handshakeDetectionTimeoutMillis = getHandshakeDetectionTimeoutMillis(config); } @@ -97,6 +106,18 @@ public class SSLContextAndOptions { return configureSSLServerSocket(sslServerSocket); } + public SslContext createNettyJdkSslContext(SSLContext sslContext, boolean isClientSocket) { + return new JdkSslContext( + sslContext, + isClientSocket, + cipherSuitesAsList, + IdentityCipherSuiteFilter.INSTANCE, + null, + isClientSocket ? X509Util.ClientAuth.NONE.toNettyClientAuth() : clientAuth.toNettyClientAuth(), + enabledProtocols, + false); + } + public int getHandshakeDetectionTimeoutMillis() { return handshakeDetectionTimeoutMillis; } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java index 200b573..004446a 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java @@ -80,7 +80,7 @@ public abstract class X509Util implements Closeable, AutoCloseable { } } - static final String DEFAULT_PROTOCOL = "TLSv1.2"; + public static final String DEFAULT_PROTOCOL = "TLSv1.2"; private static String[] getGCMCiphers() { return new String[] { "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", @@ -129,9 +129,15 @@ public abstract class X509Util implements Closeable, AutoCloseable { * If the config property is not set, the default value is NEED. */ public enum ClientAuth { - NONE, - WANT, - NEED; + NONE(io.netty.handler.ssl.ClientAuth.NONE), + WANT(io.netty.handler.ssl.ClientAuth.OPTIONAL), + NEED(io.netty.handler.ssl.ClientAuth.REQUIRE); + + private final io.netty.handler.ssl.ClientAuth nettyAuth; + + ClientAuth(io.netty.handler.ssl.ClientAuth nettyAuth) { + this.nettyAuth = nettyAuth; + } /** * Converts a property value to a ClientAuth enum. If the input string is empty or null, returns @@ -146,6 +152,10 @@ public abstract class X509Util implements Closeable, AutoCloseable { } return ClientAuth.valueOf(prop.toUpperCase()); } + + public io.netty.handler.ssl.ClientAuth toNettyClientAuth() { + return nettyAuth; + } } private String sslProtocolProperty = getConfigPrefix() + "protocol"; diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java index f87045f..f91a90d 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java @@ -25,6 +25,7 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -32,6 +33,7 @@ import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.X509KeyManager; @@ -43,6 +45,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; @@ -54,6 +57,9 @@ import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.ChannelGroupFuture; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.socket.SocketChannel; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OptionalSslHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; @@ -63,16 +69,32 @@ import io.netty.util.concurrent.GenericFutureListener; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.common.ClientX509Util; import org.apache.zookeeper.common.NettyUtils; +import org.apache.zookeeper.common.SSLContextAndOptions; import org.apache.zookeeper.common.X509Exception; import org.apache.zookeeper.common.X509Exception.SSLContextException; import org.apache.zookeeper.server.auth.ProviderRegistry; import org.apache.zookeeper.server.auth.X509AuthenticationProvider; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NettyServerCnxnFactory extends ServerCnxnFactory { private static final Logger LOG = LoggerFactory.getLogger(NettyServerCnxnFactory.class); + /** + * Allow client-server sockets to accept both SSL and plaintext connections + */ + public static final String PORT_UNIFICATION_KEY = "zookeeper.client.portUnification"; + private final boolean shouldUsePortUnification; + + /** + * The first byte in TLS protocol is the content type of the subsequent record. + * Handshakes use value 22 (0x16) so the first byte offered on any TCP connection + * attempting to establish a TLS connection will be this value. + * https://tools.ietf.org/html/rfc8446#page-79 + */ + private static final byte TLS_HANDSHAKE_RECORD_TYPE = 0x16; + private final ServerBootstrap bootstrap; private Channel parentChannel; private final ChannelGroup allChannels = @@ -92,6 +114,66 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { new AtomicReference<>(null); /** + * A handler that detects whether the client would like to use + * TLS or not and responds in kind. The first bytes are examined + * for the static TLS headers to make the determination and + * placed back in the stream with the correct ChannelHandler + * instantiated. + */ + class DualModeSslHandler extends OptionalSslHandler { + DualModeSslHandler(SslContext sslContext) { + super(sslContext); + } + + @Override + protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws Exception { + if (in.readableBytes() >= 5) { + super.decode(context, in, out); + } else if (in.readableBytes() > 0) { + // It requires 5 bytes to detect a proper ssl connection. In the + // case that the server receives fewer, check if we can fail to plaintext. + // This will occur when for any four letter work commands. + if (TLS_HANDSHAKE_RECORD_TYPE != in.getByte(0)) { + LOG.debug("first byte {} does not match TLS handshake, failing to plaintext", in.getByte(0)); + handleNonSsl(context); + } + } + } + + /** + * pulled directly from OptionalSslHandler to allow for access + * @param context + */ + private void handleNonSsl(ChannelHandlerContext context) { + ChannelHandler handler = this.newNonSslHandler(context); + if (handler != null) { + context.pipeline().replace(this, this.newNonSslHandlerName(), handler); + } else { + context.pipeline().remove(this); + } + } + + @Override + protected SslHandler newSslHandler(ChannelHandlerContext context, SslContext sslContext) { + NettyServerCnxn cnxn = Objects.requireNonNull(context.channel().attr(CONNECTION_ATTRIBUTE).get()); + LOG.debug("creating ssl handler for session {}", cnxn.getSessionId()); + SslHandler handler = super.newSslHandler(context, sslContext); + Future<Channel> handshakeFuture = handler.handshakeFuture(); + handshakeFuture.addListener(new CertificateVerifier(handler, cnxn)); + return handler; + } + + @Override + protected ChannelHandler newNonSslHandler(ChannelHandlerContext context) { + NettyServerCnxn cnxn = Objects.requireNonNull(context.channel().attr(CONNECTION_ATTRIBUTE).get()); + LOG.debug("creating plaintext handler for session {}", cnxn.getSessionId()); + allChannels.add(context.channel()); + addCnxn(cnxn); + return super.newNonSslHandler(context); + } + } + + /** * This is an inner class since we need to extend ChannelDuplexHandler, but * NettyServerCnxnFactory already extends ServerCnxnFactory. By making it inner * this class gets access to the member variables and methods. @@ -124,7 +206,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { SslHandler sslHandler = ctx.pipeline().get(SslHandler.class); Future<Channel> handshakeFuture = sslHandler.handshakeFuture(); handshakeFuture.addListener(new CertificateVerifier(sslHandler, cnxn)); - } else { + } else if (!shouldUsePortUnification) { allChannels.add(ctx.channel()); addCnxn(cnxn); } @@ -218,28 +300,51 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { } super.write(ctx, msg, promise); } + } - private final class CertificateVerifier implements GenericFutureListener<Future<Channel>> { - private final SslHandler sslHandler; - private final NettyServerCnxn cnxn; + final class CertificateVerifier implements GenericFutureListener<Future<Channel>> { + private final SslHandler sslHandler; + private final NettyServerCnxn cnxn; - CertificateVerifier(SslHandler sslHandler, NettyServerCnxn cnxn) { - this.sslHandler = sslHandler; - this.cnxn = cnxn; - } + CertificateVerifier(SslHandler sslHandler, NettyServerCnxn cnxn) { + this.sslHandler = sslHandler; + this.cnxn = cnxn; + } - /** - * Only allow the connection to stay open if certificate passes auth - */ - public void operationComplete(Future<Channel> future) throws SSLPeerUnverifiedException { - if (future.isSuccess()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Successful handshake with session 0x{}", - Long.toHexString(cnxn.getSessionId())); - } - SSLEngine eng = sslHandler.engine(); + /** + * Only allow the connection to stay open if certificate passes auth + */ + public void operationComplete(Future<Channel> future) { + if (future.isSuccess()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Successful handshake with session 0x{}", + Long.toHexString(cnxn.getSessionId())); + } + SSLEngine eng = sslHandler.engine(); + // Don't try to verify certificate if we didn't ask client to present one + if (eng.getNeedClientAuth() || eng.getWantClientAuth()) { SSLSession session = eng.getSession(); - cnxn.setClientCertificateChain(session.getPeerCertificates()); + try { + cnxn.setClientCertificateChain(session.getPeerCertificates()); + } catch (SSLPeerUnverifiedException e) { + if (eng.getNeedClientAuth()) { + // Certificate was requested but not present + LOG.error("Error getting peer certificates", e); + cnxn.close(); + return; + } else { + // Certificate was requested but was optional + // TODO: what auth info should we set on the connection? + final Channel futureChannel = future.getNow(); + allChannels.add(Objects.requireNonNull(futureChannel)); + addCnxn(cnxn); + return; + } + } catch (Exception e) { + LOG.error("Error getting peer certificates", e); + cnxn.close(); + return; + } String authProviderProp = System.getProperty(x509Util.getSslAuthProviderProperty(), "x509"); @@ -249,7 +354,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { ProviderRegistry.getProvider(authProviderProp); if (authProvider == null) { - LOG.error("Auth provider not found: {}", authProviderProp); + LOG.error("X509 Auth provider not found: {}", authProviderProp); cnxn.close(); return; } @@ -261,15 +366,15 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { cnxn.close(); return; } - - final Channel futureChannel = future.getNow(); - allChannels.add(Objects.requireNonNull(futureChannel)); - addCnxn(cnxn); - } else { - LOG.error("Unsuccessful handshake with session 0x{}", - Long.toHexString(cnxn.getSessionId())); - cnxn.close(); } + + final Channel futureChannel = future.getNow(); + allChannels.add(Objects.requireNonNull(futureChannel)); + addCnxn(cnxn); + } else { + LOG.error("Unsuccessful handshake with session 0x{}", + Long.toHexString(cnxn.getSessionId())); + cnxn.close(); } } } @@ -290,6 +395,18 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { NettyServerCnxnFactory() { x509Util = new ClientX509Util(); + boolean usePortUnification = Boolean.getBoolean(PORT_UNIFICATION_KEY); + LOG.info("{}={}", PORT_UNIFICATION_KEY, usePortUnification); + if (usePortUnification) { + try { + QuorumPeerConfig.configureSSLAuth(); + } catch (QuorumPeerConfig.ConfigException e) { + LOG.error("unable to set up SslAuthProvider, turning off client port unification", e); + usePortUnification = false; + } + } + this.shouldUsePortUnification = usePortUnification; + EventLoopGroup bossGroup = NettyUtils.newNioOrEpollEventLoopGroup( NettyUtils.getClientReachableLocalInetAddressCount()); EventLoopGroup workerGroup = NettyUtils.newNioOrEpollEventLoopGroup(); @@ -306,7 +423,9 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (secure) { - initSSL(pipeline); + initSSL(pipeline, false); + } else if (shouldUsePortUnification) { + initSSL(pipeline, true); } pipeline.addLast("servercnxnfactory", channelHandler); } @@ -315,37 +434,41 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { this.bootstrap.validate(); } - private synchronized void initSSL(ChannelPipeline p) + private synchronized void initSSL(ChannelPipeline p, boolean supportPlaintext) throws X509Exception, KeyManagementException, NoSuchAlgorithmException { String authProviderProp = System.getProperty(x509Util.getSslAuthProviderProperty()); - SSLContext sslContext; + SslContext nettySslContext; if (authProviderProp == null) { - sslContext = x509Util.getDefaultSSLContext(); + SSLContextAndOptions sslContextAndOptions = x509Util.getDefaultSSLContextAndOptions(); + nettySslContext = sslContextAndOptions.createNettyJdkSslContext( + sslContextAndOptions.getSSLContext(), false); } else { - sslContext = SSLContext.getInstance("TLSv1"); + SSLContext sslContext = SSLContext.getInstance(ClientX509Util.DEFAULT_PROTOCOL); X509AuthenticationProvider authProvider = - (X509AuthenticationProvider)ProviderRegistry.getProvider( + (X509AuthenticationProvider) ProviderRegistry.getProvider( System.getProperty(x509Util.getSslAuthProviderProperty(), "x509")); - if (authProvider == null) - { + if (authProvider == null) { LOG.error("Auth provider not found: {}", authProviderProp); throw new SSLContextException( "Could not create SSLContext with specified auth provider: " + - authProviderProp); + authProviderProp); } - sslContext.init(new X509KeyManager[] { authProvider.getKeyManager() }, - new X509TrustManager[] { authProvider.getTrustManager() }, - null); + sslContext.init(new X509KeyManager[]{authProvider.getKeyManager()}, + new X509TrustManager[]{authProvider.getTrustManager()}, + null); + nettySslContext = x509Util.getDefaultSSLContextAndOptions() + .createNettyJdkSslContext(sslContext,false); } - SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode(false); - sslEngine.setNeedClientAuth(true); - - p.addLast("ssl", new SslHandler(sslEngine)); - LOG.info("SSL handler added for channel: {}", p.channel()); + if (supportPlaintext) { + p.addLast("ssl", new DualModeSslHandler(nettySslContext)); + LOG.debug("dual mode SSL handler added for channel: {}", p.channel()); + } else { + p.addLast("ssl", nettySslContext.newHandler(p.channel().alloc())); + LOG.debug("SSL handler added for channel: {}", p.channel()); + } } @Override diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java index 3383319..b0d2800 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java @@ -500,7 +500,7 @@ public class QuorumPeerConfig { * If authentication scheme is configured but authentication * provider is not configured. */ - private void configureSSLAuth() throws ConfigException { + public static void configureSSLAuth() throws ConfigException { try (ClientX509Util clientX509Util = new ClientX509Util()) { String sslAuthProp = "zookeeper.authProvider." + System.getProperty(clientX509Util.getSslAuthProviderProperty(), "x509"); if (System.getProperty(sslAuthProp) == null) { diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java index 1ca3e14..7902ea4 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java @@ -28,6 +28,7 @@ import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.common.ClientX509Util; +import org.apache.zookeeper.server.NettyServerCnxnFactory; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; import org.junit.After; @@ -41,6 +42,7 @@ public class ClientSSLTest extends QuorumPeerTestBase { @Before public void setup() { + System.setProperty(NettyServerCnxnFactory.PORT_UNIFICATION_KEY, Boolean.TRUE.toString()); clientX509Util = new ClientX509Util(); String testDataPath = System.getProperty("test.data.dir", "src/test/resources/data"); System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory"); @@ -53,7 +55,8 @@ public class ClientSSLTest extends QuorumPeerTestBase { } @After - public void teardown() throws Exception { + public void teardown() { + System.clearProperty(NettyServerCnxnFactory.PORT_UNIFICATION_KEY); System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET); System.clearProperty(ZKClientConfig.SECURE_CLIENT); @@ -65,6 +68,18 @@ public class ClientSSLTest extends QuorumPeerTestBase { } /** + * This test checks that client SSL connections work in the absence of a + * secure port when port unification is set up for the plaintext port. + * + * This single client port will be tested for handling both plaintext + * and SSL traffic. + */ + @Test + public void testClientServerUnifiedPort() throws Exception { + testClientServerSSL(false); + } + + /** * This test checks that client - server SSL works in cluster setup of ZK servers, which includes: * 1. setting "secureClientPort" in "zoo.cfg" file. * 2. setting jvm flags for serverCnxn, keystore, truststore. @@ -75,6 +90,10 @@ public class ClientSSLTest extends QuorumPeerTestBase { */ @Test public void testClientServerSSL() throws Exception { + testClientServerSSL(true); + } + + public void testClientServerSSL(boolean useSecurePort) throws Exception { final int SERVER_COUNT = 3; final int clientPorts[] = new int[SERVER_COUNT]; final Integer secureClientPorts[] = new Integer[SERVER_COUNT]; @@ -82,16 +101,20 @@ public class ClientSSLTest extends QuorumPeerTestBase { for (int i = 0; i < SERVER_COUNT; i++) { clientPorts[i] = PortAssignment.unique(); secureClientPorts[i] = PortAssignment.unique(); - String server = String.format("server.%d=localhost:%d:%d:participant;localhost:%d", + String server = String.format("server.%d=127.0.0.1:%d:%d:participant;127.0.0.1:%d%n", i, PortAssignment.unique(), PortAssignment.unique(), clientPorts[i]); - sb.append(server + "\n"); + sb.append(server); } String quorumCfg = sb.toString(); MainThread[] mt = new MainThread[SERVER_COUNT]; for (int i = 0; i < SERVER_COUNT; i++) { - mt[i] = new MainThread(i, quorumCfg, secureClientPorts[i], true); + if (useSecurePort) { + mt[i] = new MainThread(i, quorumCfg, secureClientPorts[i], true); + } else { + mt[i] = new MainThread(i, quorumCfg, true); + } mt[i].start(); } @@ -103,8 +126,8 @@ public class ClientSSLTest extends QuorumPeerTestBase { for (int i = 0; i < SERVER_COUNT; i++) { Assert.assertTrue("waiting for server " + i + " being up", ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], TIMEOUT)); - - ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + secureClientPorts[i], TIMEOUT); + final int port = useSecurePort ? secureClientPorts[i] : clientPorts[i]; + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + port, TIMEOUT); // Do a simple operation to make sure the connection is fine. zk.create("/test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk.delete("/test", -1);