diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md index 7096aa0cc89..9312b1a959b 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md @@ -29,6 +29,21 @@ bin/zkCli.sh -waitforconnection -timeout 3000 -server remoteIP:2181 # connect with a custom client configuration properties file bin/zkCli.sh -client-configuration /path/to/client.properties ``` + +When connecting to a TLS-only server, provide a client configuration file with +the SSL settings. The `zookeeper.ssl.*` keys are preferred; `ssl.*` keys from a +server `zoo.cfg` are also accepted. + +```bash +# client.properties (TLS example) +zookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty +zookeeper.client.secure=true +zookeeper.ssl.trustStore.location=/path/to/client-truststore.jks +zookeeper.ssl.trustStore.password=changeit +zookeeper.ssl.keyStore.location=/path/to/client-keystore.jks +zookeeper.ssl.keyStore.password=changeit +``` + ## help Showing helps about ZooKeeper commands @@ -570,4 +585,4 @@ Gives all authentication information added into the current session. [zkshell: 3] whoami Auth scheme: User ip: 127.0.0.1 - digest: user1 \ No newline at end of file + digest: user1 diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java index a429d483834..0408ec76a34 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java @@ -21,6 +21,7 @@ import java.io.File; import java.nio.file.Path; import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.common.ClientX509Util; import org.apache.zookeeper.common.ConfigException; import org.apache.zookeeper.common.ZKConfig; @@ -59,6 +60,7 @@ public class ZKClientConfig extends ZKConfig { * Feature is disabled by default. */ public static final long ZOOKEEPER_REQUEST_TIMEOUT_DEFAULT = 0; + private static final String ZOOKEEPER_PREFIX = "zookeeper."; public static final String ZK_SASL_CLIENT_ALLOW_REVERSE_DNS = "zookeeper.sasl.client.allowReverseDnsLookup"; public static final boolean ZK_SASL_CLIENT_ALLOW_REVERSE_DNS_DEFAULT = false; /** @@ -107,6 +109,12 @@ public ZKClientConfig(Path configPath) throws ConfigException { super(configPath); } + @Override + public void addConfiguration(Path configPath) throws ConfigException { + super.addConfiguration(configPath); + applyServerSslConfiguration(); + } + /** * Initialize all the ZooKeeper client properties which are configurable as * java system property @@ -139,6 +147,43 @@ protected void handleBackwardCompatibility() { setProperty(DNS_SRV_REFRESH_INTERVAL_SECONDS, System.getProperty(DNS_SRV_REFRESH_INTERVAL_SECONDS)); } + private void applyServerSslConfiguration() { + try (ClientX509Util clientX509Util = new ClientX509Util()) { + copyServerSslProperty(clientX509Util.getSslProtocolProperty()); + copyServerSslProperty(clientX509Util.getSslEnabledProtocolsProperty()); + copyServerSslProperty(clientX509Util.getSslCipherSuitesProperty()); + copyServerSslProperty(clientX509Util.getSslKeystoreLocationProperty()); + copyServerSslProperty(clientX509Util.getSslKeystorePasswdProperty()); + copyServerSslProperty(clientX509Util.getSslKeystorePasswdPathProperty()); + copyServerSslProperty(clientX509Util.getSslKeystoreTypeProperty()); + copyServerSslProperty(clientX509Util.getSslTruststoreLocationProperty()); + copyServerSslProperty(clientX509Util.getSslTruststorePasswdProperty()); + copyServerSslProperty(clientX509Util.getSslTruststorePasswdPathProperty()); + copyServerSslProperty(clientX509Util.getSslTruststoreTypeProperty()); + copyServerSslProperty(clientX509Util.getSslContextSupplierClassProperty()); + copyServerSslProperty(clientX509Util.getSslHostnameVerificationEnabledProperty()); + copyServerSslProperty(clientX509Util.getSslCrlEnabledProperty()); + copyServerSslProperty(clientX509Util.getSslOcspEnabledProperty()); + copyServerSslProperty(clientX509Util.getSslClientAuthProperty()); + copyServerSslProperty(clientX509Util.getSslHandshakeDetectionTimeoutMillisProperty()); + copyServerSslProperty(clientX509Util.getSslAuthProviderProperty()); + } + } + + private void copyServerSslProperty(String clientProperty) { + if (clientProperty == null || getProperty(clientProperty) != null) { + return; + } + if (!clientProperty.startsWith(ZOOKEEPER_PREFIX)) { + return; + } + String serverProperty = clientProperty.substring(ZOOKEEPER_PREFIX.length()); + String serverValue = getProperty(serverProperty); + if (serverValue != null) { + setProperty(clientProperty, serverValue); + } + } + /** * Returns true if the SASL client is enabled. By default, the client is * enabled but can be disabled by setting the system property diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/client/ZKClientConfigTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/client/ZKClientConfigTest.java index 95846b467a3..5d4b1d169ce 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/client/ZKClientConfigTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/client/ZKClientConfigTest.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import org.apache.zookeeper.common.ClientX509Util; import org.apache.zookeeper.common.ConfigException; import org.apache.zookeeper.common.ZKConfig; import org.junit.jupiter.api.Test; @@ -125,6 +126,34 @@ public void testReadConfigurationFile(@TempDir File testDataDir) throws IOExcept assertEquals(conf.getProperty("dummyProperty"), "dummyValue"); } + @Test + @Timeout(value = 10) + public void testServerSslPropertyFallback(@TempDir File testDataDir) throws IOException, ConfigException { + File file = File.createTempFile("clientConfigSsl", ".conf", testDataDir); + Properties clientConfProp = new Properties(); + clientConfProp.setProperty("ssl.trustStore.location", "/tmp/server-truststore.jks"); + clientConfProp.setProperty("ssl.trustStore.password", "server-pass"); + clientConfProp.setProperty("ssl.keyStore.location", "/tmp/server-keystore.jks"); + clientConfProp.setProperty("ssl.keyStore.password", "server-pass"); + clientConfProp.setProperty("zookeeper.ssl.trustStore.location", "/tmp/client-truststore.jks"); + OutputStream io = new FileOutputStream(file); + try { + clientConfProp.store(io, "Client Configurations"); + } finally { + io.close(); + } + + ZKClientConfig conf = new ZKClientConfig(); + conf.addConfiguration(Paths.get(file.getAbsolutePath())); + + try (ClientX509Util clientX509Util = new ClientX509Util()) { + assertEquals("/tmp/client-truststore.jks", conf.getProperty(clientX509Util.getSslTruststoreLocationProperty())); + assertEquals("server-pass", conf.getProperty(clientX509Util.getSslTruststorePasswdProperty())); + assertEquals("/tmp/server-keystore.jks", conf.getProperty(clientX509Util.getSslKeystoreLocationProperty())); + assertEquals("server-pass", conf.getProperty(clientX509Util.getSslKeystorePasswdProperty())); + } + } + @Test @Timeout(value = 10) public void testSetConfiguration() {