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);

Reply via email to