Add SSL support for Zookeeper client

Zookeeper server from version 3.5.x is able to server traffic over SSL.
With the fix in Zookeeper version 3.5.7 skipping client SSL
authentication is possible. Bump Zookeeper client library to 3.5.7 to
match Zookeeper service version.

Add Gerrit configuration to use SSL for Zookeeper client.

Feature: Issue 12583
Change-Id: I75b9b89e8994a517a9030906efa01415ed5bce6c
diff --git a/BUILD b/BUILD
index 80c7dda..9238349 100644
--- a/BUILD
+++ b/BUILD
@@ -22,6 +22,8 @@
         "@curator-recipes//jar",
         "@global-refdb//jar",
         "@zookeeper//jar",
+        "@zookeeper-jute//jar",
+        "@netty-all//jar",
     ],
 )
 
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index c9905be..55f28d6 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -29,8 +29,20 @@
 
     maven_jar(
         name = "zookeeper",
-        artifact = "org.apache.zookeeper:zookeeper:3.4.14",
-        sha1 = "c114c1e1c8172a7cd3f6ae39209a635f7a06c1a1",
+        artifact = "org.apache.zookeeper:zookeeper:3.5.7",
+        sha1 = "12bdf55ba8be7fc891996319d37f35eaad7e63ea",
+    )
+
+    maven_jar(
+        name = "zookeeper-jute",
+        artifact = "org.apache.zookeeper:zookeeper-jute:3.5.7",
+        sha1 = "1270f80b08904499a6839a2ee1800da687ad96b4",
+    )
+
+    maven_jar(
+        name = "netty-all",
+        artifact = "io.netty:netty-all:4.1.45.Final",
+        sha1 = "e830eae36d22f2bba3118a3bc08e17f15263a01d",
     )
 
     maven_jar(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperConfig.java b/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperConfig.java
index 7523d1f..b03ff3e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperConfig.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
+import java.util.Optional;
 import org.apache.commons.lang.StringUtils;
 import org.apache.curator.RetryPolicy;
 import org.apache.curator.framework.CuratorFramework;
@@ -41,6 +42,7 @@
   private final int DEFAULT_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS = 300;
   private final int DEFAULT_CAS_RETRY_POLICY_MAX_RETRIES = 3;
   private final int DEFAULT_TRANSACTION_LOCK_TIMEOUT = 1000;
+  private final boolean DEFAULT_SSH_CONNECTION = false;
 
   static {
     CuratorFrameworkFactory.Builder b = CuratorFrameworkFactory.builder();
@@ -56,6 +58,13 @@
   public static final String KEY_RETRY_POLICY_MAX_SLEEP_TIME_MS = "retryPolicyMaxSleepTimeMs";
   public static final String KEY_RETRY_POLICY_MAX_RETRIES = "retryPolicyMaxRetries";
   public static final String KEY_ROOT_NODE = "rootNode";
+  public static final String SSL_CONNECTION = "sslConnection";
+  public static final String SSL_KEYSTORE_LOCATION = "sslKeyStoreLocation";
+  public static final String SSL_TRUSTSTORE_LOCATION = "sslTrustStoreLocation";
+
+  public static final String SSL_KEYSTORE_PASSWORD = "sslKeyStorePassword";
+  public static final String SSL_TRUSTSTORE_PASSWORD = "sslTrustStorePassword";
+
   public final String KEY_CAS_RETRY_POLICY_BASE_SLEEP_TIME_MS = "casRetryPolicyBaseSleepTimeMs";
   public final String KEY_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS = "casRetryPolicyMaxSleepTimeMs";
   public final String KEY_CAS_RETRY_POLICY_MAX_RETRIES = "casRetryPolicyMaxRetries";
@@ -72,7 +81,14 @@
   private final int casMaxSleepTimeMs;
   private final int casMaxRetries;
 
+  private Boolean isSshEnabled;
+  private Optional<String> sslKeyStoreLocation;
+  private Optional<String> sslTrustStoreLocation;
+  private Optional<String> sslKeyStorePassword;
+  private Optional<String> sslTrustStorePassword;
+
   public static final String SECTION = "ref-database";
+
   private final Long transactionLockTimeOut;
 
   private CuratorFramework build;
@@ -145,10 +161,39 @@
             TRANSACTION_LOCK_TIMEOUT_KEY,
             DEFAULT_TRANSACTION_LOCK_TIMEOUT);
 
+    isSshEnabled =
+        getBoolean(zkConfig, SECTION, SUBSECTION, SSL_CONNECTION, DEFAULT_SSH_CONNECTION);
+
+    sslKeyStoreLocation = getOptionalString(zkConfig, SECTION, SUBSECTION, SSL_KEYSTORE_LOCATION);
+
+    sslTrustStoreLocation =
+        getOptionalString(zkConfig, SECTION, SUBSECTION, SSL_TRUSTSTORE_LOCATION);
+
+    sslKeyStorePassword = getOptionalString(zkConfig, SECTION, SUBSECTION, SSL_KEYSTORE_PASSWORD);
+
+    sslTrustStorePassword =
+        getOptionalString(zkConfig, SECTION, SUBSECTION, SSL_TRUSTSTORE_PASSWORD);
+
     checkArgument(StringUtils.isNotEmpty(connectionString), "zookeeper.%s contains no servers");
   }
 
   public CuratorFramework buildCurator() {
+    if (isSshEnabled) {
+
+      System.setProperty(
+          "zookeeper.clientCnxnSocket", "org.apache.zookeeper.ClientCnxnSocketNetty");
+      System.setProperty("zookeeper.client.secure", "true");
+
+      sslKeyStoreLocation.ifPresent(
+          location -> System.setProperty("zookeeper.ssl.keyStore.location", location));
+      sslTrustStoreLocation.ifPresent(
+          location -> System.setProperty("zookeeper.ssl.trustStore.location", location));
+      sslKeyStorePassword.ifPresent(
+          passw -> System.setProperty("zookeeper.ssl.keyStore.password", passw));
+      sslTrustStorePassword.ifPresent(
+          passw -> System.setProperty("zookeeper.ssl.trustStore.password", passw));
+    }
+
     if (build == null) {
       this.build =
           CuratorFrameworkFactory.builder()
@@ -194,6 +239,11 @@
     }
   }
 
+  private Optional<String> getOptionalString(
+      Config cfg, String section, String subsection, String name) {
+    return Optional.ofNullable(cfg.getString(section, subsection, name)).filter(s -> !s.isEmpty());
+  }
+
   private String getString(
       Config cfg, String section, String subsection, String name, String defaultValue) {
     String value = cfg.getString(section, subsection, name);
@@ -202,4 +252,15 @@
     }
     return defaultValue;
   }
+
+  private Boolean getBoolean(
+      Config cfg, String section, String subSection, String name, Boolean defaultValue) {
+    try {
+      return cfg.getBoolean(section, subSection, name, defaultValue);
+    } catch (IllegalArgumentException e) {
+      log.error("invalid value for {}; using default value {}", name, defaultValue);
+      log.debug("Failed to retrieve boolean value: {}", e.getMessage(), e);
+      return defaultValue;
+    }
+  }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 5cd89a5..48c8517 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -81,3 +81,38 @@
     acquires the exclusive lock for a reference.
 
     Defaults: 1000
+
+```ref-database.zookeeper.sslConnection```
+:   Enable ssl for Zookeeper connection.
+
+    Defaults: false
+
+```ref-database.zookeeper.sslKeyStoreLocation```
+:   Configuration for the path to the ssl key store location.
+
+    Defaults: "./etc/keystore.jks"
+
+```ref-database.zookeeper.sslTrustStoreLocation```
+:   Configuration for the path to the local ssl trust store location.
+
+    Defaults: "./etc/truststore.jks"
+
+
+File '@PLUGIN@.secure.config'
+--------------------
+
+## Sample configuration.
+
+```
+[ref-database "zookeeper"]
+    sslKeyStorePassword = test_passw
+    sslTrustStorePassword = test_passw
+```
+
+## Configuration parameters
+
+```ref-database.zookeeper.sslKeyStorePassword```
+:   Configuration for the password to the ssl key store location.
+
+```ref-database.zookeeper.sslTrustStorePassword```
+:   Configuration for the password to the ssl trust store location.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java b/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
index b948886..995d0f8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
@@ -33,7 +33,7 @@
 public class ZookeeperTestContainerSupport {
 
   static class ZookeeperContainer extends GenericContainer<ZookeeperContainer> {
-    public static String ZOOKEEPER_VERSION = "3.4.13";
+    public static String ZOOKEEPER_VERSION = "3.5.5";
 
     public ZookeeperContainer() {
       super("zookeeper:" + ZOOKEEPER_VERSION);