This is an automated email from the ASF dual-hosted git repository.
kezhuw pushed a commit to branch branch-3.9
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/branch-3.9 by this push:
new 927c09750 ZOOKEEPER-4958: Fix client hostname verification ignored in
server if ssl.authProvider configured
927c09750 is described below
commit 927c0975028de74fa4cfd9a58ec74d3b18c09d64
Author: Kezhu Wang <[email protected]>
AuthorDate: Sun Dec 14 21:46:58 2025 +0800
ZOOKEEPER-4958: Fix client hostname verification ignored in server if
ssl.authProvider configured
Reviewers: anmolnar
Author: kezhuw
Closes #2303 from kezhuw/ZOOKEEPER-4958-client-server-hostname-verification
(cherry picked from commit a7fe813aa3fee50c1a653b92e7f91e4c67f977ba)
Signed-off-by: Kezhu Wang <[email protected]>
---
.../server/auth/KeyAuthenticationProvider.java | 2 +-
.../server/auth/X509AuthenticationProvider.java | 3 +-
.../server/SSLHostnameVerificationTest.java | 529 +++++++++++++++++++++
3 files changed, 532 insertions(+), 2 deletions(-)
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
index 92bb380fb..cf252b9b0 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
@@ -37,7 +37,7 @@
* See the "Pluggable ZooKeeper authentication" section of the
* "Zookeeper Programmer's Guide" for general details of implementing an
* authentication plugin. e.g.
- *
http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication
+ *
http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication
*
* This class looks for a numeric "key" under the /key node.
* Authorization is granted if the user passes in as authorization a number
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
index 1c8edcb86..3fcb28978 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
@@ -88,6 +88,7 @@ public X509AuthenticationProvider() throws X509Exception {
boolean crlEnabled =
Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty()));
boolean ocspEnabled =
Boolean.parseBoolean(config.getProperty(x509Util.getSslOcspEnabledProperty()));
boolean hostnameVerificationEnabled =
Boolean.parseBoolean(config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
+ boolean clientHostnameVerificationEnabled =
x509Util.isClientHostnameVerificationEnabled(config);
boolean allowReverseDnsLookup =
Boolean.parseBoolean(config.getProperty(x509Util.getSslAllowReverseDnsLookupProperty()));
X509KeyManager km = null;
@@ -120,7 +121,7 @@ public X509AuthenticationProvider() throws X509Exception {
crlEnabled,
ocspEnabled,
hostnameVerificationEnabled,
- false,
+ clientHostnameVerificationEnabled,
allowReverseDnsLookup,
fipsMode);
} catch (TrustManagerException e) {
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
new file mode 100644
index 000000000..bee6abb4b
--- /dev/null
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
@@ -0,0 +1,529 @@
+/*
+ * 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.zookeeper.server;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Security;
+import java.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.client.ZKClientConfig;
+import org.apache.zookeeper.common.ssl.Ca;
+import org.apache.zookeeper.common.ssl.Cert;
+import org.apache.zookeeper.server.embedded.ExitHandler;
+import org.apache.zookeeper.server.embedded.ZooKeeperServerEmbedded;
+import org.apache.zookeeper.test.ClientBase;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.burningwave.tools.net.HostResolutionRequestInterceptor;
+import org.burningwave.tools.net.MappedHostResolver;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class SSLHostnameVerificationTest {
+ @BeforeAll
+ public static void setupDNSMocks() {
+ Map<String, String> hostAliases = new LinkedHashMap<>();
+
+ // avoid resolving "localhost" to ipv6 address "::1"
+ hostAliases.put("localhost", "127.0.0.1");
+ HostResolutionRequestInterceptor.INSTANCE.install(new
MappedHostResolver(hostAliases));
+ HostResolutionRequestInterceptor.INSTANCE.clearCache();
+ }
+
+ @AfterAll
+ public static void clearDNSMocks() {
+ HostResolutionRequestInterceptor.INSTANCE.uninstall();
+ }
+
+ @BeforeAll
+ public static void setup() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @AfterAll
+ public static void cleanup() {
+ Security.removeProvider("BC");
+ }
+
+ Watcher.Event.KeeperState checkConnectState(String connectString,
ZKClientConfig clientConfig) throws Exception {
+ Duration timeout = Duration.ofSeconds(1);
+ Watcher.Event.KeeperState state;
+ CompletableFuture<WatchedEvent> future = new CompletableFuture<>();
+ try (ZooKeeper zk = new ZooKeeper(connectString, (int)
timeout.toMillis(), future::complete, clientConfig)) {
+ try {
+ WatchedEvent event = future.get(timeout.toMillis() * 2,
TimeUnit.MILLISECONDS);
+ state = event.getState();
+ } catch (TimeoutException ignored) {
+ // See: ZOOKEEPER-4508, ZOOKEEPER-4921, ZOOKEEPER-4923
+ state = Watcher.Event.KeeperState.Expired;
+ }
+ }
+ return state;
+ }
+
+ @ParameterizedTest(name = "{0}, fips-mode: {1}")
+ @CsvSource({
+ "localhost, true",
+ "localhost, false",
+ "127.0.0.1, true",
+ "127.0.0.1, false",
+ })
+ public void testClientHostnameVerificationWithMismatchNames(String
serverHost, boolean fipsEnabled, @TempDir Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server with cert mismatching cn/dns/ip
+ Cert server1Cert =
ca.signer("abc0").withDnsName("abc1").withIpAddress("192.168.0.10").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+ config.put("secureClientPortAddress", serverHost);
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.fips-mode",
Boolean.toString(fipsEnabled));
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+
+ // when: connect using mismatched dns/ip
+ String connectionString = server.getSecureConnectionString();
+ // then: connection rejected by us as no matching name
+ assertEquals(Watcher.Event.KeeperState.Expired,
checkConnectState(server.getSecureConnectionString(), clientConfig));
+ }
+ }
+ }
+
+ @Test
+ public void testClientHostnameVerificationWithMatchingCnName(@TempDir Path
tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server cert with cn name "localhost"
+ Cert server1Cert = ca.signer("localhost").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+ // when: connect using matching dns
+ String connectionString = "localhost:" +
server.getSecureClientPort();
+ // then: connected as there is no other sans
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @Test
+ public void
testClientHostnameVerificationWithMatchingReversedDnsName(@TempDir Path tmpDir)
throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server cert with cn name "localhost"
+ Cert server1Cert = ca.signer("localhost").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+ // when: connect using matching reversed dns
+ String connectionString = "127.0.0.1:" +
server.getSecureClientPort();
+
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+ // then: connected as there is no other sans
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+
+ @Test
+ public void
testClientHostnameVerificationWithMatchingDisabledReversedDnsName(@TempDir Path
tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server cert with cn name "localhost"
+ Cert server1Cert = ca.signer("localhost").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+ // when: connect using matching reversed dns
+ String connectionString = "127.0.0.1:" +
server.getSecureClientPort();
+
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "false");
+ // then: connected as there is no other sans
+ assertEquals(Watcher.Event.KeeperState.Expired,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+ @ParameterizedTest
+ @ValueSource(strings = {"localhost", "127.0.0.1"})
+ public void
testClientHostnameVerificationWithMatchingCnNameButMismatchingSan(String
serverHost, @TempDir Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server with cert matching cn
+ Cert server1Cert =
ca.signer("localhost").withDnsName("abc1").withIpAddress("192.168.0.10").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+ // when: connect with dns or ip resolved to cn name
+ String connectionString = String.format("%s:%d", serverHost,
server.getSecureClientPort());
+
+ // then: fail to connect
+ //
+ // CN matching has been deprecated by rfc2818 and can be used
+ // as fallback only when no subjectAlts are available
+ assertEquals(Watcher.Event.KeeperState.Expired,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"localhost", "127.0.0.1"})
+ public void testClientHostnameVerificationWithMatchingIpAddress(String
serverHost, @TempDir Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server with cert mismatching ip
+ Cert server1Cert =
ca.signer("abc0").withDnsName("abc1").withIpAddress("127.0.0.1").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+ // when: connect with matching ip or its dns
+ String connectionString = String.format("%s:%d", serverHost,
server.getSecureClientPort());
+
+ // then: connected
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @Test
+ public void testClientHostnameVerificationFipsModeWithIpAddress(@TempDir
Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server with cert mismatching ip
+ Cert server1Cert =
ca.signer("abc0").withDnsName("abc1").withIpAddress("127.0.0.1").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.fips-mode", "true");
+
+ // when: connect with ip's dns
+ String connectionString = String.format("localhost:%d",
server.getSecureClientPort());
+ // then: rejected as fips-mode don't do dns lookup
+ assertEquals(Watcher.Event.KeeperState.Expired,
checkConnectState(connectionString, clientConfig));
+
+ // when: connect with ip address
+ connectionString = String.format("127.0.0.1:%d",
server.getSecureClientPort());
+ // then: connected
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @Test
+ public void testClientHostnameVerificationFipsModeWithDns(@TempDir Path
tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server cert with dns "localhost"
+ Cert server1Cert =
ca.signer("abc0").withDnsName("localhost").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.fips-mode", "true");
+
+ // when: connect with ip address
+ String connectionString = String.format("127.0.0.1:%d",
server.getSecureClientPort());
+ // then: fail as fips-mode won't do reverse dns lookup
+ assertEquals(Watcher.Event.KeeperState.Expired,
checkConnectState(connectionString, clientConfig));
+
+ // when: connect with "localhost"
+ connectionString = String.format("localhost:%d",
server.getSecureClientPort());
+ // then: succeed
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @Test
+ public void testClientHostnameVerificationWithMatchingDnsName(@TempDir
Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ // given: server cert with dns name "localhost"
+ Cert server1Cert =
ca.signer("abc0").withDnsName("localhost").withIpAddress("192.168.0.10").sign();
+ Properties config = server1Cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "false");
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ Cert clientCert = ca.sign("client");
+ ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+ clientConfig.setProperty("zookeeper.sasl.client", "false");
+ clientConfig.setProperty("zookeeper.fips-mode", "false");
+ clientConfig.setProperty("zookeeper.ssl.hostnameVerification",
"true");
+
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+
+ // when: connect to "127.0.0.1"
+ String connectionString = String.format("127.0.0.1:%d",
server.getSecureClientPort());
+ // then: connected as ZKHostnameVerifier will do reverse dns
lookup
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+
+ // when: connect to "localhost"
+ connectionString = String.format("localhost:%d",
server.getSecureClientPort());
+ // then: connected as dns match
+ assertEquals(Watcher.Event.KeeperState.SyncConnected,
checkConnectState(connectionString, clientConfig));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"x509", ""})
+ public void testServerHostnameVerification(String authProvider, @TempDir
Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ Cert cert = ca.sign("server");
+
+ // given: server with client hostname verification enabled
+ Properties config = cert.buildServerProperties(ca);
+ config.put("ssl.hostnameVerification", "true");
+ config.put("ssl.clientHostnameVerification", "true");
+ config.put("ssl.allowReverseDnsLookup", "false");
+ config.put("fips-mode", "false");
+ if (!authProvider.isEmpty()) {
+ config.put("ssl.authProvider", "x509");
+ }
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ // // when: connect with matching dns name
+ Cert client1Cert =
ca.signer("client1").withDnsName("localhost").sign();
+ ZKClientConfig client1Config =
client1Cert.buildClientConfig(ca);
+
client1Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+ // then: connected
+
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000,
true, client1Config));
+
+ // when: connect with matching ip address
+ Cert client2Cert =
ca.signer("client2").withIpAddress("127.0.0.1").sign();
+ ZKClientConfig client2Config =
client2Cert.buildClientConfig(ca);
+
client2Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+ // then: connected
+
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000,
true, client2Config));
+
+ // when: connect with matching cn name
+ Cert client3Cert = ca.signer("localhost").sign();
+ ZKClientConfig client3Config =
client3Cert.buildClientConfig(ca);
+
client3Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+ // then: connected
+
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000,
true, client3Config));
+
+ // when: connect with mismatching cert name
+ Cert client4Cert =
ca.signer("client4").withDnsName("abc").sign();
+ ZKClientConfig client4Config =
client4Cert.buildClientConfig(ca);
+
client4Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+ // then: fail to connect
+
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(),
6000, true, client4Config));
+ }
+ }
+ }
+
+ /**
+ * FIPS mode disallow custom trust manager so server has no way to
validate against client's endpoint.
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {"x509", ""})
+ public void testServerHostnameVerificationFipsMode(String authProvider,
@TempDir Path tmpDir) throws Exception {
+ try (Ca ca = Ca.create(tmpDir)) {
+ Cert cert = ca.sign("server");
+
+ Properties config = cert.buildServerProperties(ca);
+
+ // given: server in fips mode with client hostname verification
enabled
+ config.put("ssl.hostnameVerification", "true");
+ config.put("ssl.clientHostnameVerification", "true");
+ config.put("fips-mode", "true");
+
+ if (!authProvider.isEmpty()) {
+ config.put("ssl.authProvider", "x509");
+ }
+
+ try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+ .builder()
+ .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+ .configuration(config)
+ .exitHandler(ExitHandler.LOG_ONLY)
+ .build()) {
+ server.start();
+
+ // server ready
+
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+ // // when: connect with matching dns name
+ Cert client1Cert =
ca.signer("localhost").withResolvedDns("localhost").sign();
+ ZKClientConfig client1Config =
client1Cert.buildClientConfig(ca);
+
client1Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+ // then: fail to connect
+
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(),
6000, true, client1Config));
+ }
+ }
+ }
+}