From e0a339223a89367d9391758ff62c99923369b5ee Mon Sep 17 00:00:00 2001 From: Szilard Nemeth Date: Sun, 4 Jun 2023 14:40:41 -0400 Subject: [PATCH] HADOOP-18709. Add curator based ZooKeeper communication support over SSL/TLS into the common library. Contributed by Ferenc Erdelyi --- hadoop-common-project/hadoop-common/pom.xml | 8 + .../hadoop/fs/CommonConfigurationKeys.java | 8 + .../hadoop/util/curator/ZKCuratorManager.java | 222 ++++++++++++++++-- .../src/main/resources/core-default.xml | 29 +++ .../curator/TestSecureZKCuratorManager.java | 214 +++++++++++++++++ .../curator/resources/data/ssl/keystore.jks | Bin 0 -> 4160 bytes .../curator/resources/data/ssl/truststore.jks | Bin 0 -> 994 bytes 7 files changed, 458 insertions(+), 23 deletions(-) create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/TestSecureZKCuratorManager.java create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/keystore.jks create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/truststore.jks diff --git a/hadoop-common-project/hadoop-common/pom.xml b/hadoop-common-project/hadoop-common/pom.xml index 58006c011d..426f7a4af4 100644 --- a/hadoop-common-project/hadoop-common/pom.xml +++ b/hadoop-common-project/hadoop-common/pom.xml @@ -340,6 +340,14 @@ + + io.netty + netty-handler + + + io.netty + netty-transport-native-epoll + io.dropwizard.metrics metrics-core diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java index 9d6224366d..63f494a20a 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeys.java @@ -417,6 +417,14 @@ public class CommonConfigurationKeys extends CommonConfigurationKeysPublic { /** How often to retry a ZooKeeper operation in milliseconds. */ public static final String ZK_RETRY_INTERVAL_MS = ZK_PREFIX + "retry-interval-ms"; + /** Keystore location for ZooKeeper client connection over SSL. */ + public static final String ZK_SSL_KEYSTORE_LOCATION = ZK_PREFIX + "ssl.keystore.location"; + /** Keystore password for ZooKeeper client connection over SSL. */ + public static final String ZK_SSL_KEYSTORE_PASSWORD = ZK_PREFIX + "ssl.keystore.password"; + /** Truststore location for ZooKeeper client connection over SSL. */ + public static final String ZK_SSL_TRUSTSTORE_LOCATION = ZK_PREFIX + "ssl.truststore.location"; + /** Truststore password for ZooKeeper client connection over SSL. */ + public static final String ZK_SSL_TRUSTSTORE_PASSWORD = ZK_PREFIX + "ssl.truststore.password"; public static final int ZK_RETRY_INTERVAL_MS_DEFAULT = 1000; /** Default domain name resolver for hadoop to use. */ public static final String HADOOP_DOMAINNAME_RESOLVER_IMPL = diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/curator/ZKCuratorManager.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/curator/ZKCuratorManager.java index c11b868386..81ee4663e1 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/curator/ZKCuratorManager.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/curator/ZKCuratorManager.java @@ -23,6 +23,7 @@ import java.util.LinkedList; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.curator.framework.AuthInfo; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; @@ -39,13 +40,17 @@ import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ClientX509Util; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.util.Preconditions; +import javax.naming.ConfigurationException; + /** * Helper class that provides utility methods specific to ZK operations. */ @@ -122,7 +127,7 @@ public static List getZKAuths(Configuration conf) * Start the connection to the ZooKeeper ensemble. * @throws IOException If the connection cannot be started. */ - public void start() throws IOException { + public void start() throws IOException{ this.start(new ArrayList<>()); } @@ -132,6 +137,20 @@ public void start() throws IOException { * @throws IOException If the connection cannot be started. */ public void start(List authInfos) throws IOException { + this.start(authInfos, false); + } + + /** + * Start the connection to the ZooKeeper ensemble. + * + * @param authInfos List of authentication keys. + * @param sslEnabled If the connection should be SSL/TLS encrypted. + * @throws IOException If the connection cannot be started. + */ + public void start(List authInfos, boolean sslEnabled) + throws IOException{ + + ZKClientConfig zkClientConfig = new ZKClientConfig(); // Connect to the ZooKeeper ensemble String zkHostPort = conf.get(CommonConfigurationKeys.ZK_ADDRESS); @@ -139,6 +158,8 @@ public void start(List authInfos) throws IOException { throw new IOException( CommonConfigurationKeys.ZK_ADDRESS + " is not configured."); } + LOG.debug("Configured {} as {}", CommonConfigurationKeys.ZK_ADDRESS, zkHostPort); + int numRetries = conf.getInt(CommonConfigurationKeys.ZK_NUM_RETRIES, CommonConfigurationKeys.ZK_NUM_RETRIES_DEFAULT); int zkSessionTimeout = conf.getInt(CommonConfigurationKeys.ZK_TIMEOUT_MS, @@ -156,21 +177,49 @@ public void start(List authInfos) throws IOException { for (ZKUtil.ZKAuthInfo zkAuth : zkAuths) { authInfos.add(new AuthInfo(zkAuth.getScheme(), zkAuth.getAuth())); } - - CuratorFramework client = CuratorFrameworkFactory.builder() - .connectString(zkHostPort) - .zookeeperFactory(new HadoopZookeeperFactory( - conf.get(CommonConfigurationKeys.ZK_SERVER_PRINCIPAL), - conf.get(CommonConfigurationKeys.ZK_KERBEROS_PRINCIPAL), - conf.get(CommonConfigurationKeys.ZK_KERBEROS_KEYTAB))) - .sessionTimeoutMs(zkSessionTimeout) - .retryPolicy(retryPolicy) - .authorization(authInfos) - .build(); + if (sslEnabled) { + validateSslConfiguration(conf); + } + CuratorFramework client = CuratorFrameworkFactory.builder().connectString(zkHostPort) + .zookeeperFactory( + new HadoopZookeeperFactory(conf.get(CommonConfigurationKeys.ZK_SERVER_PRINCIPAL), + conf.get(CommonConfigurationKeys.ZK_KERBEROS_PRINCIPAL), + conf.get(CommonConfigurationKeys.ZK_KERBEROS_KEYTAB), sslEnabled, + new TruststoreKeystore(conf))).zkClientConfig(zkClientConfig) + .sessionTimeoutMs(zkSessionTimeout).retryPolicy(retryPolicy) + .authorization(authInfos).build(); client.start(); this.curator = client; } + /* Check on SSL/TLS client connection requirements to emit the name of the + configuration missing. It improves supportability. */ + private void validateSslConfiguration(Configuration config) throws IOException { + if (StringUtils.isEmpty(config.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION))) { + throw new IOException( + "The SSL encryption is enabled for the component's ZooKeeper client connection, " + + "however the " + CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION + " " + + "parameter is empty."); + } + if (StringUtils.isEmpty(config.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD))) { + throw new IOException( + "The SSL encryption is enabled for the component's " + "ZooKeeper client connection, " + + "however the " + CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD + " " + + "parameter is empty."); + } + if (StringUtils.isEmpty(config.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION))) { + throw new IOException( + "The SSL encryption is enabled for the component's ZooKeeper client connection, " + + "however the " + CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION + " " + + "parameter is empty."); + } + if (StringUtils.isEmpty(config.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD))) { + throw new IOException( + "The SSL encryption is enabled for the component's ZooKeeper client connection, " + + "however the " + CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD + " " + + "parameter is empty."); + } + } /** * Get ACLs for a ZNode. @@ -414,14 +463,14 @@ public class SafeTransaction { throws Exception { this.fencingNodePath = fencingNodePath; curatorOperations.add(curator.transactionOp().create() - .withMode(CreateMode.PERSISTENT) - .withACL(fencingACL) - .forPath(fencingNodePath, new byte[0])); + .withMode(CreateMode.PERSISTENT) + .withACL(fencingACL) + .forPath(fencingNodePath, new byte[0])); } public void commit() throws Exception { curatorOperations.add(curator.transactionOp().delete() - .forPath(fencingNodePath)); + .forPath(fencingNodePath)); curator.transaction().forOperations(curatorOperations); curatorOperations.clear(); } @@ -429,21 +478,21 @@ public void commit() throws Exception { public void create(String path, byte[] data, List acl, CreateMode mode) throws Exception { curatorOperations.add(curator.transactionOp().create() - .withMode(mode) - .withACL(acl) - .forPath(path, data)); + .withMode(mode) + .withACL(acl) + .forPath(path, data)); } public void delete(String path) throws Exception { curatorOperations.add(curator.transactionOp().delete() - .forPath(path)); + .forPath(path)); } public void setData(String path, byte[] data, int version) throws Exception { curatorOperations.add(curator.transactionOp().setData() - .withVersion(version) - .forPath(path, data)); + .withVersion(version) + .forPath(path, data)); } } @@ -452,21 +501,53 @@ public static class HadoopZookeeperFactory implements ZookeeperFactory { private final String zkPrincipal; private final String kerberosPrincipal; private final String kerberosKeytab; + private final Boolean sslEnabled; + private final TruststoreKeystore truststoreKeystore; + /** + * Constructor for the helper class to configure the ZooKeeper client connection. + * @param zkPrincipal Optional. + */ public HadoopZookeeperFactory(String zkPrincipal) { this(zkPrincipal, null, null); } + /** + * Constructor for the helper class to configure the ZooKeeper client connection. + * @param zkPrincipal Optional. + * @param kerberosPrincipal Optional. Use along with kerberosKeytab. + * @param kerberosKeytab Optional. Use along with kerberosPrincipal. + */ public HadoopZookeeperFactory(String zkPrincipal, String kerberosPrincipal, String kerberosKeytab) { + this(zkPrincipal, kerberosPrincipal, kerberosKeytab, false, + new TruststoreKeystore(new Configuration())); + } + + /** + * Constructor for the helper class to configure the ZooKeeper client connection. + * + * @param zkPrincipal Optional. + * @param kerberosPrincipal Optional. Use along with kerberosKeytab. + * @param kerberosKeytab Optional. Use along with kerberosPrincipal. + * @param sslEnabled Flag to enable SSL/TLS ZK client connection for each component + * independently. + * @param truststoreKeystore TruststoreKeystore object containing the keystoreLocation, + * keystorePassword, truststoreLocation, truststorePassword for + * SSL/TLS connection when sslEnabled is set to true. + */ + public HadoopZookeeperFactory(String zkPrincipal, String kerberosPrincipal, + String kerberosKeytab, boolean sslEnabled, TruststoreKeystore truststoreKeystore) { this.zkPrincipal = zkPrincipal; this.kerberosPrincipal = kerberosPrincipal; this.kerberosKeytab = kerberosKeytab; + this.sslEnabled = sslEnabled; + this.truststoreKeystore = truststoreKeystore; } @Override public ZooKeeper newZooKeeper(String connectString, int sessionTimeout, - Watcher watcher, boolean canBeReadOnly + Watcher watcher, boolean canBeReadOnly ) throws Exception { ZKClientConfig zkClientConfig = new ZKClientConfig(); if (zkPrincipal != null) { @@ -478,10 +559,65 @@ public ZooKeeper newZooKeeper(String connectString, int sessionTimeout, if (zkClientConfig.isSaslClientEnabled() && !isJaasConfigurationSet(zkClientConfig)) { setJaasConfiguration(zkClientConfig); } + if (sslEnabled) { + setSslConfiguration(zkClientConfig); + } return new ZooKeeper(connectString, sessionTimeout, watcher, canBeReadOnly, zkClientConfig); } + /** + * Configure ZooKeeper Client with SSL/TLS connection. + * @param zkClientConfig ZooKeeper Client configuration + */ + private void setSslConfiguration(ZKClientConfig zkClientConfig) throws ConfigurationException { + this.setSslConfiguration(zkClientConfig, new ClientX509Util()); + } + + private void setSslConfiguration(ZKClientConfig zkClientConfig, ClientX509Util x509Util) + throws ConfigurationException { + validateSslConfiguration(); + LOG.info("Configuring the ZooKeeper client to use SSL/TLS encryption for connecting to the " + + "ZooKeeper server."); + LOG.debug("Configuring the ZooKeeper client with {} location: {}.", + this.truststoreKeystore.keystoreLocation, + CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION); + LOG.debug("Configuring the ZooKeeper client with {} location: {}.", + this.truststoreKeystore.truststoreLocation, + CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION); + + zkClientConfig.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); + zkClientConfig.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, + "org.apache.zookeeper.ClientCnxnSocketNetty"); + zkClientConfig.setProperty(x509Util.getSslKeystoreLocationProperty(), + this.truststoreKeystore.keystoreLocation); + zkClientConfig.setProperty(x509Util.getSslKeystorePasswdProperty(), + this.truststoreKeystore.keystorePassword); + zkClientConfig.setProperty(x509Util.getSslTruststoreLocationProperty(), + this.truststoreKeystore.truststoreLocation); + zkClientConfig.setProperty(x509Util.getSslTruststorePasswdProperty(), + this.truststoreKeystore.truststorePassword); + } + + private void validateSslConfiguration() throws ConfigurationException { + if (StringUtils.isEmpty(this.truststoreKeystore.keystoreLocation)) { + throw new ConfigurationException( + "The keystore location parameter is empty for the ZooKeeper client connection."); + } + if (StringUtils.isEmpty(this.truststoreKeystore.keystorePassword)) { + throw new ConfigurationException( + "The keystore password parameter is empty for the ZooKeeper client connection."); + } + if (StringUtils.isEmpty(this.truststoreKeystore.truststoreLocation)) { + throw new ConfigurationException( + "The truststore location parameter is empty for the ZooKeeper client connection."); + } + if (StringUtils.isEmpty(this.truststoreKeystore.truststorePassword)) { + throw new ConfigurationException( + "The truststore password parameter is empty for the ZooKeeper client connection."); + } + } + private boolean isJaasConfigurationSet(ZKClientConfig zkClientConfig) { String clientConfig = zkClientConfig.getProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT); @@ -503,4 +639,44 @@ private void setJaasConfiguration(ZKClientConfig zkClientConfig) throws IOExcept zkClientConfig.setProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, JAAS_CLIENT_ENTRY); } } + + /** + * Helper class to contain the Truststore/Keystore paths for the ZK client connection over + * SSL/TLS. + */ + public static class TruststoreKeystore { + private final String keystoreLocation; + private final String keystorePassword; + private final String truststoreLocation; + private final String truststorePassword; + + /** + * Configuration for the ZooKeeper connection when SSL/TLS is enabled. + * When a value is not configured, ensure that empty string is set instead of null. + * + * @param conf ZooKeeper Client configuration + */ + public TruststoreKeystore(Configuration conf) { + keystoreLocation = conf.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION, ""); + keystorePassword = conf.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD, ""); + truststoreLocation = conf.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION, ""); + truststorePassword = conf.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD, ""); + } + + public String getKeystoreLocation() { + return keystoreLocation; + } + + public String getKeystorePassword() { + return keystorePassword; + } + + public String getTruststoreLocation() { + return truststoreLocation; + } + + public String getTruststorePassword() { + return truststorePassword; + } + } } \ No newline at end of file diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 537feebaec..34dadf3160 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml @@ -3924,6 +3924,35 @@ The switch to turn S3A auditing on or off. the ZK CLI). + + + hadoop.zk.ssl.keystore.location + + Keystore location for ZooKeeper client connection over SSL. + + + + + hadoop.zk.ssl.keystore.password + + Keystore password for ZooKeeper client connection over SSL. + + + + + hadoop.zk.ssl.truststore.location + + Truststore location for ZooKeeper client connection over SSL. + + + + + hadoop.zk.ssl.truststore.password + + Truststore password for ZooKeeper client connection over SSL. + + + hadoop.system.tags YARN,HDFS,NAMENODE,DATANODE,REQUIRED,SECURITY,KERBEROS,PERFORMANCE,CLIENT diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/TestSecureZKCuratorManager.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/TestSecureZKCuratorManager.java new file mode 100644 index 0000000000..d83279a941 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/TestSecureZKCuratorManager.java @@ -0,0 +1,214 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.util.curator; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.apache.curator.test.InstanceSpec; +import org.apache.curator.test.TestingServer; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeys; +import org.apache.zookeeper.ClientCnxnSocketNetty; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ClientX509Util; +import org.apache.zookeeper.server.NettyServerCnxnFactory; + +import static org.apache.hadoop.fs.FileContext.LOG; +import static org.junit.Assert.assertEquals; + +/** + * Test the manager for ZooKeeper Curator when SSL/TLS is enabled for the ZK server-client + * connection. + */ +public class TestSecureZKCuratorManager { + + public static final boolean DELETE_DATA_DIRECTORY_ON_CLOSE = true; + private TestingServer server; + private ZKCuratorManager curator; + private Configuration hadoopConf; + static final int SECURE_CLIENT_PORT = 2281; + static final int JUTE_MAXBUFFER = 400000000; + static final File ZK_DATA_DIR = new File("testZkSSLClientConnectionDataDir"); + private static final int SERVER_ID = 1; + private static final int TICK_TIME = 100; + private static final int MAX_CLIENT_CNXNS = 10; + public static final int ELECTION_PORT = -1; + public static final int QUORUM_PORT = -1; + + @Before + public void setup() throws Exception { + // inject values to the ZK configuration file for secure connection + Map customConfiguration = new HashMap<>(); + customConfiguration.put("secureClientPort", String.valueOf(SECURE_CLIENT_PORT)); + customConfiguration.put("audit.enable", true); + this.hadoopConf = setUpSecureConfig(); + InstanceSpec spec = + new InstanceSpec(ZK_DATA_DIR, SECURE_CLIENT_PORT, ELECTION_PORT, QUORUM_PORT, + DELETE_DATA_DIRECTORY_ON_CLOSE, SERVER_ID, TICK_TIME, MAX_CLIENT_CNXNS, + customConfiguration); + this.server = new TestingServer(spec, true); + this.hadoopConf.set(CommonConfigurationKeys.ZK_ADDRESS, this.server.getConnectString()); + this.curator = new ZKCuratorManager(this.hadoopConf); + this.curator.start(new ArrayList<>(), true); + } + + /** + * A static method to configure the test ZK server to accept secure client connection. + * The self-signed certificates were generated for testing purposes as described below. + * For the ZK client to connect with the ZK server, the ZK server's keystore and truststore + * should be used. + * For testing purposes the keystore and truststore were generated using default values. + * 1. to generate the keystore.jks file: + * # keytool -genkey -alias mockcert -keyalg RSA -keystore keystore.jks -keysize 2048 + * 2. generate the ca-cert and the ca-key: + * # openssl req -new -x509 -keyout ca-key -out ca-cert + * 3. to generate the certificate signing request (cert-file): + * # keytool -keystore keystore.jks -alias mockcert -certreq -file certificate-request + * 4. to generate the ca-cert.srl file and make the cert valid for 10 years: + * # openssl x509 -req -CA ca-cert -CAkey ca-key -in certificate-request -out cert-signed + * -days 3650 -CAcreateserial -passin pass:password + * 5. add the ca-cert to the keystore.jks: + * # keytool -keystore keystore.jks -alias mockca -import -file ca-cert + * 6. install the signed certificate to the keystore: + * # keytool -keystore keystore.jks -alias mockcert -import -file cert-signed + * 7. add the certificate to the truststore: + * # keytool -keystore truststore.jks -alias mockcert -import -file ca-cert + * For our purpose, we only need the end result of this process: the keystore.jks and the + * truststore.jks files. + * + * @return conf The method returns the updated Configuration. + */ + public static Configuration setUpSecureConfig() { + return setUpSecureConfig(new Configuration(), + "src/test/java/org/apache/hadoop/util/curator" + "/resources/data"); + } + + public static Configuration setUpSecureConfig(Configuration conf, String testDataPath) { + System.setProperty("zookeeper.serverCnxnFactory", + NettyServerCnxnFactory.class.getCanonicalName()); + + System.setProperty("zookeeper.ssl.keyStore.location", testDataPath + "keystore.jks"); + System.setProperty("zookeeper.ssl.keyStore.password", "password"); + System.setProperty("zookeeper.ssl.trustStore.location", testDataPath + "truststore.jks"); + System.setProperty("zookeeper.ssl.trustStore.password", "password"); + System.setProperty("zookeeper.request.timeout", "12345"); + + System.setProperty("jute.maxbuffer", String.valueOf(JUTE_MAXBUFFER)); + + System.setProperty("javax.net.debug", "ssl"); + System.setProperty("zookeeper.authProvider.x509", + "org.apache.zookeeper.server.auth.X509AuthenticationProvider"); + + conf.set(CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION, + testDataPath + "/ssl/keystore.jks"); + conf.set(CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD, "password"); + conf.set(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION, + testDataPath + "/ssl/truststore.jks"); + conf.set(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD, "password"); + return conf; + } + + @After + public void teardown() throws Exception { + this.curator.close(); + if (this.server != null) { + this.server.close(); + this.server = null; + } + } + + @Test + public void testSecureZKConfiguration() throws Exception { + LOG.info("Entered to the testSecureZKConfiguration test case."); + // Validate that HadoopZooKeeperFactory will set ZKConfig with given principals + ZKCuratorManager.HadoopZookeeperFactory factory = + new ZKCuratorManager.HadoopZookeeperFactory(null, null, null, true, + new ZKCuratorManager.TruststoreKeystore(hadoopConf)); + ZooKeeper zk = factory.newZooKeeper(this.server.getConnectString(), 1000, null, false); + validateSSLConfiguration(this.hadoopConf.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION), + this.hadoopConf.get(CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD), + this.hadoopConf.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION), + this.hadoopConf.get(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD), zk); + } + + private void validateSSLConfiguration(String keystoreLocation, String keystorePassword, + String truststoreLocation, String truststorePassword, ZooKeeper zk) { + try (ClientX509Util x509Util = new ClientX509Util()) { + //testing if custom values are set properly + assertEquals("Validate that expected clientConfig is set in ZK config", keystoreLocation, + zk.getClientConfig().getProperty(x509Util.getSslKeystoreLocationProperty())); + assertEquals("Validate that expected clientConfig is set in ZK config", keystorePassword, + zk.getClientConfig().getProperty(x509Util.getSslKeystorePasswdProperty())); + assertEquals("Validate that expected clientConfig is set in ZK config", truststoreLocation, + zk.getClientConfig().getProperty(x509Util.getSslTruststoreLocationProperty())); + assertEquals("Validate that expected clientConfig is set in ZK config", truststorePassword, + zk.getClientConfig().getProperty(x509Util.getSslTruststorePasswdProperty())); + } + //testing if constant values hardcoded into the code are set properly + assertEquals("Validate that expected clientConfig is set in ZK config", Boolean.TRUE.toString(), + zk.getClientConfig().getProperty(ZKClientConfig.SECURE_CLIENT)); + assertEquals("Validate that expected clientConfig is set in ZK config", + ClientCnxnSocketNetty.class.getCanonicalName(), + zk.getClientConfig().getProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET)); + } + + @Test + public void testTruststoreKeystoreConfiguration() { + LOG.info("Entered to the testTruststoreKeystoreConfiguration test case."); + /* + By default the truststore/keystore configurations are not set, hence the values are null. + Validate that the null values are converted into empty strings by the class. + */ + Configuration conf = new Configuration(); + ZKCuratorManager.TruststoreKeystore truststoreKeystore = + new ZKCuratorManager.TruststoreKeystore(conf); + + assertEquals("Validate that null value is converted to empty string.", "", + truststoreKeystore.getKeystoreLocation()); + assertEquals("Validate that null value is converted to empty string.", "", + truststoreKeystore.getKeystorePassword()); + assertEquals("Validate that null value is converted to empty string.", "", + truststoreKeystore.getTruststoreLocation()); + assertEquals("Validate that null value is converted to empty string.", "", + truststoreKeystore.getTruststorePassword()); + + //Validate that non-null values will remain intact + conf.set(CommonConfigurationKeys.ZK_SSL_KEYSTORE_LOCATION, "/keystore.jks"); + conf.set(CommonConfigurationKeys.ZK_SSL_KEYSTORE_PASSWORD, "keystorePassword"); + conf.set(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_LOCATION, "/truststore.jks"); + conf.set(CommonConfigurationKeys.ZK_SSL_TRUSTSTORE_PASSWORD, "truststorePassword"); + ZKCuratorManager.TruststoreKeystore truststoreKeystore1 = + new ZKCuratorManager.TruststoreKeystore(conf); + assertEquals("Validate that non-null value kept intact.", "/keystore.jks", + truststoreKeystore1.getKeystoreLocation()); + assertEquals("Validate that null value is converted to empty string.", "keystorePassword", + truststoreKeystore1.getKeystorePassword()); + assertEquals("Validate that null value is converted to empty string.", "/truststore.jks", + truststoreKeystore1.getTruststoreLocation()); + assertEquals("Validate that null value is converted to empty string.", "truststorePassword", + truststoreKeystore1.getTruststorePassword()); + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/keystore.jks b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..89792c1705e27753fbb7066211f72275bf74ba55 GIT binary patch literal 4160 zcmeH~XHe7G7Qj;o5UP<9Kt(}7N`QZ;5+MpKf}j*pqzae~O(b-9U?@R~NTeu5q)QQy z-UVr5=uJRCq_?F@2c;~!`*xi1&AgfS<-IqvAMS_q`*P1cbAIREd$4q{1OkB|e=aZx zdgmSv1Of*(hHYK~fnZ>OM70kvQPYV~gQ>v65Cj+u15uHv=KKsB+HbWuG>Y!aY=e{z zdfV>i8B;!p@XkyCv>Ll%lW`A3)jvzb_|Rx>2ZY>HsTLNqR|M+iY$1_N*`DH1O5Yie zP*s67Ff(aaxA$7u)|5mYnR|9OkZ~rfw)MGNZ(_TsPg!%p0JwXwLP<;kQH8AfB7hfN zA*5`k3iVlijVh`eUoFcYKp8lsmN}}hm@isW!h29rH%8Mhz7OIKrweyT%w=)-qZ99!VIG8nk{fqpV5=FiAIPnE$rj+ zTq(YCogmPY7=x2A;bTRv-`qFlHi19QTQ?O z1+V4P(Qx&zz8rolglturFEUw$NyIe~;k)f8C1+gMaSUvlj9&t~h? z^mvKFoV{&fCHyKu_S_vjzWW@r#R||vPOS}%p)R-^5gyhTGTFiRJP|~Y?FCYTmKWJ#c`v}y(r;&XU-a-%MN$7IrvSWN& za8-`8b)0x!9i~K70oTU2temCX_TZ6K8 zNbsWjCk#q1xnq5WmA~0)t7?4HF#r>Mfs-+{RW2vyZI^Oy5$e?r5)wzZ5woOo!*Ws^ z0`0JYpHV=0#FKnJC&#W^EnIqmlnuJiSkr~-MY|E@HZJ(^&&Z93SS0~;!)RSAW zd_`W_vmiiZuiLrpG$M@NNOv7mzXb4o(5r=J&{943CBj4*vMX+RDH zg8Bm_s4qZ*SVQ2TPpPaNkNX<}0Y+-LaDZCCHsjH7gn9a5C zYTp=HF{-`ENf#nJ364pIm+QGnNZwMa=0QElLM5~xmi98`Za6ZItrL}^HLEq`yz{8? z!kGC30Pd#F_voMHI*#)SdiireyKR?=D1I3;Zo z{M4Ih6Lwo(^06Xc?}sQ0EOLl+%79Yhtzj`pb8 zn7`@ts+4lan4dJuPF+b>MP@%+Adj1vacTu-EE!;hMkYo|448_gQe<;XDCv>)MVE+$ zJ{)pX1G7V4Mi6_?5^k1WwQ9*U3MX7}+n#w15Qcd@BdS`B3Iam)R~-&FE1<%jBAB35 z!I>>ef>+9M==ogU`<))j*wo(SQ}wVA<3|pKR{hNG3)-N+d`v&`G3Cg|;3FRgf_pd% zhg?#AtQh_OfscSJAams7k)G1u^!y(FZw`@vC+tuxDsv%YT&AqVe)E2bn-+zipxh`C zov%9Q>C;KB9#_+3fe?jji%(xU{&MC~Fm?6XjJQRK;6*S!Gse|31lHqQA|>)^OJAp7 zeQK4Zhf4)zjNb%JUy2+!n4S!jSd%XfkVvA zQ1!o#TR_d}2|Vo^kuPJP^EnSr+-!Qt_L#hYnLa3Y5GHjn)~I4-X!Rt49CFUEkq)mC zaQmR??t!<>>Gw16vmIq4)G{3v4o)38_8Ltgic0z^iVmtgHd_~5t{GU(EiTJkCt;jP zx}pKcU3%m}Qh2&6h^y|%*-p&~yAeYb@=wAZwcmGPp&MOg{7+q%jCPHb&p{pdBA?{4 zT~DB6Dbuy=9!xjq@Vncua%EDyVRpl9Fxv91?+35J->xe8PYIoPLQLbM1K6y`+yL1< zW1(tEUD*$7Tq20WQDH@QY5#djwyP!`JJd?LnLa5k_?kzk_^HYgSM_tT19d07uKEQZ zx@eJ;r(Owc_^2K35!-ovKi;7&y@+|;&AdbKQ>H=g#D(@c$?SZv4`{46OT*gBJI~G7 zlXZeYNiPy)lx?Ym1iDnN$X&MBs))DOAqj0a8}Pc+pjG{$1I36Y5DcR$z4-i zFOnp%!VlgplBsjq$80Jp$VF`Km2HCrx}*X*{qL73HO}0TKyd!!ccB&fx5WRQm%!f= f|1I%x|z4#ypG^ literal 0 HcmV?d00001 diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/truststore.jks b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/curator/resources/data/ssl/truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..c78514c834bfc755014e1ab06afa2ef25a2d0fb7 GIT binary patch literal 994 zcmezO_TO6u1_mY|W(3pBNx8{DcKh)u<)sX)5qhQumJAHc%M6;B7a24$bun==+~~S2 zx#@Q4A_HDFPOUbNw(q=*jNGgY291*pxeYkkm_u3EgqfUu3cpgeWq9f^^~S%akK2tt*=%VuT9$b@VdB1Yxy2=pB83cg z0<~r$vlwpluHB?psB)=; z-Oh|7%-CRU@7lZ3fl5vWasN(a|EtfEzx0e#`P}werXuy|v6seb$YG(se$~UH?n{dlx=gqxPl2iW`u+*-=rsUEhBQ7jaNTBUOz3bvY+9(g`RKvE78{gZrEz& literal 0 HcmV?d00001