blob: d83279a941462a1d81eaad322a6cc3bc4ac85972 [file] [log] [blame]
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<String, Object> 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());
}
}