This is an automated email from the ASF dual-hosted git repository.
symat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new b2c1b5a ZOOKEEPER-4396: Read Key/trust store password from file
b2c1b5a is described below
commit b2c1b5af36a196ca2e9080750d212aa1a0550f7c
Author: liwang <[email protected]>
AuthorDate: Fri Jan 28 08:27:11 2022 +0000
ZOOKEEPER-4396: Read Key/trust store password from file
Key/trust store password is currently specified as plain text via system
property or config property. To avoid exposing passwords as plain text and
reduce security vulnerability, we provide the support of reading passwords from
files.
The following four properties are added:
1. ssl.keyStore.passwordPath
2. ssl.quorum.keyStore.passwordPath
3. ssl.trustStore.passwordPath
4. ssl.quorum.trustStore.passwordPath
Specifies the file path that contains the key/trust store password. Reading
the password from a file takes precedence over the explicit password property.
Author: liwang <[email protected]>
Reviewers: hristopher Tubbs <[email protected]>, Enrico Olivelli
<[email protected]>, Mate Szalay-Beko <[email protected]>
Closes #1773 from li4wang/ZOOKEEPER-4396
---
.../src/main/resources/markdown/zookeeperAdmin.md | 12 ++++
.../resources/markdown/zookeeperProgrammers.md | 10 ++-
.../org/apache/zookeeper/common/SecretUtils.java | 53 ++++++++++++++++
.../java/org/apache/zookeeper/common/X509Util.java | 34 +++++++++-
.../java/org/apache/zookeeper/common/ZKConfig.java | 2 +
.../zookeeper/server/admin/JettyAdminServer.java | 27 +++++++-
.../server/auth/X509AuthenticationProvider.java | 13 +++-
.../common/BaseX509ParameterizedTestCase.java | 4 +-
.../apache/zookeeper/common/SecretUtilsTest.java | 70 ++++++++++++++++++++
.../apache/zookeeper/common/X509TestContext.java | 2 +
.../org/apache/zookeeper/common/X509UtilTest.java | 74 ++++++++++++++++++++++
.../server/admin/JettyAdminServerTest.java | 15 +++++
.../zookeeper/server/quorum/QuorumSSLTest.java | 27 ++++++++
.../org/apache/zookeeper/test/ClientSSLTest.java | 17 +++++
14 files changed, 352 insertions(+), 8 deletions(-)
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index ce5dcaa..0b658d0 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -1642,6 +1642,12 @@ and [SASL authentication for
ZooKeeper](https://cwiki.apache.org/confluence/disp
Specifies the file path to a Java keystore containing the local
credentials to be used for client and quorum TLS connections, and the
password to unlock the file.
+
+* *ssl.keyStore.passwordPath* and *ssl.quorum.keyStore.passwordPath* :
+ (Java system properties: **zookeeper.ssl.keyStore.passwordPath** and
**zookeeper.ssl.quorum.keyStore.passwordPath**)
+ **New in 3.8.0:**
+ Specifies the file path that contains the keystore password. Reading the
password from a file takes precedence over
+ the explicit password property.
* *ssl.keyStore.type* and *ssl.quorum.keyStore.type* :
(Java system properties: **zookeeper.ssl.keyStore.type** and
**zookeeper.ssl.quorum.keyStore.type**)
@@ -1658,6 +1664,12 @@ and [SASL authentication for
ZooKeeper](https://cwiki.apache.org/confluence/disp
credentials to be used for client and quorum TLS connections, and the
password to unlock the file.
+* *ssl.trustStore.passwordPath* and *ssl.quorum.trustStore.passwordPath* :
+ (Java system properties: **zookeeper.ssl.trustStore.passwordPath** and
**zookeeper.ssl.quorum.trustStore.passwordPath**)
+ **New in 3.8.0:**
+ Specifies the file path that contains the truststore password. Reading the
password from a file takes precedence over
+ the explicit password property.
+
* *ssl.trustStore.type* and *ssl.quorum.trustStore.type* :
(Java system properties: **zookeeper.ssl.trustStore.type** and
**zookeeper.ssl.quorum.trustStore.type**)
**New in 3.5.5:**
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md
index 7c0d4ec..c2933e2 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md
@@ -1341,11 +1341,19 @@ and [SASL authentication for
ZooKeeper](https://cwiki.apache.org/confluence/disp
**New in 3.5.5:**
Specifies the file path to a JKS containing the local credentials to be
used for SSL connections,
and the password to unlock the file.
-
+
+* *zookeeper.ssl.keyStore.passwordPath* :
+ **New in 3.8.0:**
+ Specifies the file path which contains the keystore password
+
* *zookeeper.ssl.trustStore.location and zookeeper.ssl.trustStore.password* :
**New in 3.5.5:**
Specifies the file path to a JKS containing the remote credentials to be
used for SSL connections,
and the password to unlock the file.
+
+* *zookeeper.ssl.trustStore.passwordPath* :
+ **New in 3.8.0:**
+ Specifies the file path which contains the truststore password
* *zookeeper.ssl.keyStore.type* and *zookeeper.ssl.trustStore.type*:
**New in 3.5.5:**
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/SecretUtils.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/SecretUtils.java
new file mode 100644
index 0000000..f20c9ce
--- /dev/null
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/SecretUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for handling secret such as key/trust store password
+ */
+public final class SecretUtils {
+ private static final Logger LOG =
LoggerFactory.getLogger(SecretUtils.class);
+
+ private SecretUtils() {
+ }
+
+ public static char[] readSecret(final String pathToFile) {
+ LOG.info("Reading secret from {}", pathToFile);
+
+ try {
+ final String secretValue = new String(
+ Files.readAllBytes(Paths.get(pathToFile)),
StandardCharsets.UTF_8);
+
+ if (secretValue.endsWith(System.lineSeparator())) {
+ return secretValue.substring(0, secretValue.length() -
System.lineSeparator().length()).toCharArray();
+ }
+
+ return secretValue.toCharArray();
+ } catch (final Throwable e) {
+ LOG.error("Exception occurred when reading secret from file {}",
pathToFile, e);
+ throw new IllegalStateException("Exception occurred when reading
secret from file " + pathToFile, e);
+ }
+ }
+}
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
index 52cb5fe..0cc3eb2 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
@@ -148,9 +148,11 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
private String cipherSuitesProperty = getConfigPrefix() + "ciphersuites";
private String sslKeystoreLocationProperty = getConfigPrefix() +
"keyStore.location";
private String sslKeystorePasswdProperty = getConfigPrefix() +
"keyStore.password";
+ private String sslKeystorePasswdPathProperty = getConfigPrefix() +
"keyStore.passwordPath";
private String sslKeystoreTypeProperty = getConfigPrefix() +
"keyStore.type";
private String sslTruststoreLocationProperty = getConfigPrefix() +
"trustStore.location";
private String sslTruststorePasswdProperty = getConfigPrefix() +
"trustStore.password";
+ private String sslTruststorePasswdPathProperty = getConfigPrefix() +
"trustStore.passwordPath";
private String sslTruststoreTypeProperty = getConfigPrefix() +
"trustStore.type";
private String sslContextSupplierClassProperty = getConfigPrefix() +
"context.supplier.class";
private String sslHostnameVerificationEnabledProperty = getConfigPrefix()
+ "hostnameVerification";
@@ -202,6 +204,10 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
return sslKeystorePasswdProperty;
}
+ public String getSslKeystorePasswdPathProperty() {
+ return sslKeystorePasswdPathProperty;
+ }
+
public String getSslKeystoreTypeProperty() {
return sslKeystoreTypeProperty;
}
@@ -214,6 +220,10 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
return sslTruststorePasswdProperty;
}
+ public String getSslTruststorePasswdPathProperty() {
+ return sslTruststorePasswdPathProperty;
+ }
+
public String getSslTruststoreTypeProperty() {
return sslTruststoreTypeProperty;
}
@@ -334,7 +344,7 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
TrustManager[] trustManagers = null;
String keyStoreLocationProp =
config.getProperty(sslKeystoreLocationProperty, "");
- String keyStorePasswordProp =
config.getProperty(sslKeystorePasswdProperty, "");
+ String keyStorePasswordProp =
getPasswordFromConfigPropertyOrFile(config, sslKeystorePasswdProperty,
sslKeystorePasswdPathProperty);
String keyStoreTypeProp = config.getProperty(sslKeystoreTypeProperty);
// There are legal states in some use cases for null KeyManager or
TrustManager.
@@ -354,7 +364,7 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
}
String trustStoreLocationProp =
config.getProperty(sslTruststoreLocationProperty, "");
- String trustStorePasswordProp =
config.getProperty(sslTruststorePasswdProperty, "");
+ String trustStorePasswordProp =
getPasswordFromConfigPropertyOrFile(config, sslTruststorePasswdProperty,
sslTruststorePasswdPathProperty);
String trustStoreTypeProp =
config.getProperty(sslTruststoreTypeProperty);
boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
@@ -414,6 +424,26 @@ public abstract class X509Util implements Closeable,
AutoCloseable {
}
/**
+ * Returns the password specified by the given property or from the file
specified by the given path property.
+ * If both are specified, the value stored in the file will be returned.
+ *
+ * @param config Zookeeper configuration
+ * @param propertyName property name
+ * @param pathPropertyName path property name
+ * @return the password value
+ */
+ public String getPasswordFromConfigPropertyOrFile(final ZKConfig config,
+ final String
propertyName,
+ final String
pathPropertyName) {
+ String value = config.getProperty(propertyName, "");
+ final String pathProperty = config.getProperty(pathPropertyName, "");
+ if (!pathProperty.isEmpty()) {
+ value = String.valueOf(SecretUtils.readSecret(pathProperty));
+ }
+ return value;
+ }
+
+ /**
* Creates a key manager by loading the key store from the given file of
* the given type, optionally decrypting it using the given password.
* @param keyStoreLocation the location of the key store file.
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
index 6bbe698..1d1a6c9 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
@@ -118,9 +118,11 @@ public class ZKConfig {
properties.put(x509Util.getSslCipherSuitesProperty(),
System.getProperty(x509Util.getSslCipherSuitesProperty()));
properties.put(x509Util.getSslKeystoreLocationProperty(),
System.getProperty(x509Util.getSslKeystoreLocationProperty()));
properties.put(x509Util.getSslKeystorePasswdProperty(),
System.getProperty(x509Util.getSslKeystorePasswdProperty()));
+ properties.put(x509Util.getSslKeystorePasswdPathProperty(),
System.getProperty(x509Util.getSslKeystorePasswdPathProperty()));
properties.put(x509Util.getSslKeystoreTypeProperty(),
System.getProperty(x509Util.getSslKeystoreTypeProperty()));
properties.put(x509Util.getSslTruststoreLocationProperty(),
System.getProperty(x509Util.getSslTruststoreLocationProperty()));
properties.put(x509Util.getSslTruststorePasswdProperty(),
System.getProperty(x509Util.getSslTruststorePasswdProperty()));
+ properties.put(x509Util.getSslTruststorePasswdPathProperty(),
System.getProperty(x509Util.getSslTruststorePasswdPathProperty()));
properties.put(x509Util.getSslTruststoreTypeProperty(),
System.getProperty(x509Util.getSslTruststoreTypeProperty()));
properties.put(x509Util.getSslContextSupplierClassProperty(),
System.getProperty(x509Util.getSslContextSupplierClassProperty()));
properties.put(x509Util.getSslHostnameVerificationEnabledProperty(),
System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
index d2c7217..99241dc 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/admin/JettyAdminServer.java
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.zookeeper.common.QuorumX509Util;
+import org.apache.zookeeper.common.SecretUtils;
import org.apache.zookeeper.common.X509Util;
import org.apache.zookeeper.server.ZooKeeperServer;
import org.eclipse.jetty.http.HttpVersion;
@@ -121,10 +122,15 @@ public class JettyAdminServer implements AdminServer {
try (QuorumX509Util x509Util = new QuorumX509Util()) {
String privateKeyType =
System.getProperty(x509Util.getSslKeystoreTypeProperty(), "");
String privateKeyPath =
System.getProperty(x509Util.getSslKeystoreLocationProperty(), "");
- String privateKeyPassword =
System.getProperty(x509Util.getSslKeystorePasswdProperty(), "");
+ String privateKeyPassword =
getPasswordFromSystemPropertyOrFile(
+ x509Util.getSslKeystorePasswdProperty(),
+ x509Util.getSslKeystorePasswdPathProperty());
+
String certAuthType =
System.getProperty(x509Util.getSslTruststoreTypeProperty(), "");
String certAuthPath =
System.getProperty(x509Util.getSslTruststoreLocationProperty(), "");
- String certAuthPassword =
System.getProperty(x509Util.getSslTruststorePasswdProperty(), "");
+ String certAuthPassword = getPasswordFromSystemPropertyOrFile(
+ x509Util.getSslTruststorePasswdProperty(),
+ x509Util.getSslTruststorePasswdPathProperty());
KeyStore keyStore = null, trustStore = null;
try {
@@ -289,4 +295,21 @@ public class JettyAdminServer implements AdminServer {
ctxHandler.setSecurityHandler(securityHandler);
}
+
+ /**
+ * Returns the password specified by the given property or stored in the
file specified by the
+ * given path property. If both are specified, the password stored in the
file will be returned.
+ * @param propertyName the name of the property
+ * @param pathPropertyName the name of the path property
+ * @return password value
+ */
+ private String getPasswordFromSystemPropertyOrFile(final String
propertyName,
+ final String
pathPropertyName) {
+ String value = System.getProperty(propertyName, "");
+ final String pathValue = System.getProperty(pathPropertyName, "");
+ if (!pathValue.isEmpty()) {
+ value = String.valueOf(SecretUtils.readSecret(pathValue));
+ }
+ return value;
+ }
}
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 1f42b24..255e5cf 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
@@ -45,6 +45,9 @@ import org.slf4j.LoggerFactory;
* <br>To specify store passwords, set the following system properties:
* <br><code>zookeeper.ssl.keyStore.password</code>
* <br><code>zookeeper.ssl.trustStore.password</code>
+ * <br>Alternatively, the passwords can be specified by the following password
file path properties:
+ * <br><code>zookeeper.ssl.keyStore.passwordPath</code>
+ * <br><code>zookeeper.ssl.trustStore.passwordPath</code>
* <br>Alternatively, this can be plugged with any X509TrustManager and
* X509KeyManager implementation.
*/
@@ -61,13 +64,17 @@ public class X509AuthenticationProvider implements
AuthenticationProvider {
* <br><code>zookeeper.ssl.keyStore.location</code>
* <br><code>zookeeper.ssl.trustStore.location</code>
* <br><code>zookeeper.ssl.keyStore.password</code>
+ * <br><code>zookeeper.ssl.keyStore.passwordPath</code>
* <br><code>zookeeper.ssl.trustStore.password</code>
+ * <br><code>zookeeper.ssl.trustStore.passwordPath</code>
*/
public X509AuthenticationProvider() throws X509Exception {
ZKConfig config = new ZKConfig();
try (X509Util x509Util = new ClientX509Util()) {
String keyStoreLocation =
config.getProperty(x509Util.getSslKeystoreLocationProperty(), "");
- String keyStorePassword =
config.getProperty(x509Util.getSslKeystorePasswdProperty(), "");
+ String keyStorePassword =
x509Util.getPasswordFromConfigPropertyOrFile(config,
+ x509Util.getSslKeystorePasswdProperty(),
+ x509Util.getSslKeystorePasswdPathProperty());
String keyStoreTypeProp =
config.getProperty(x509Util.getSslKeystoreTypeProperty());
boolean crlEnabled =
Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty()));
@@ -87,7 +94,9 @@ public class X509AuthenticationProvider implements
AuthenticationProvider {
}
String trustStoreLocation =
config.getProperty(x509Util.getSslTruststoreLocationProperty(), "");
- String trustStorePassword =
config.getProperty(x509Util.getSslTruststorePasswdProperty(), "");
+ String trustStorePassword =
x509Util.getPasswordFromConfigPropertyOrFile(config,
+ x509Util.getSslTruststorePasswdProperty(),
+ x509Util.getSslTruststorePasswdPathProperty());
String trustStoreTypeProp =
config.getProperty(x509Util.getSslTruststoreTypeProperty());
if (trustStoreLocation.isEmpty()) {
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java
index 8c3731b..344faef 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java
@@ -42,6 +42,8 @@ import org.junit.jupiter.params.provider.Arguments;
* and caching the X509TestContext objects used by the tests.
*/
public abstract class BaseX509ParameterizedTestCase extends ZKTestCase {
+ protected static final String KEY_NON_EMPTY_PASSWORD = "pa$$w0rd";
+ protected static final String KEY_EMPTY_PASSWORD = "";
/**
* Default parameters suitable for most subclasses. See example usage
@@ -53,7 +55,7 @@ public abstract class BaseX509ParameterizedTestCase extends
ZKTestCase {
int paramIndex = 0;
for (X509KeyType caKeyType : X509KeyType.values()) {
for (X509KeyType certKeyType : X509KeyType.values()) {
- for (String keyPassword : new String[]{"", "pa$$w0rd"}) {
+ for (String keyPassword : new String[]{KEY_EMPTY_PASSWORD,
KEY_NON_EMPTY_PASSWORD}) {
result.add(Arguments.of(caKeyType, certKeyType,
keyPassword, paramIndex++));
}
}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/SecretUtilsTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/SecretUtilsTest.java
new file mode 100644
index 0000000..796cf5f
--- /dev/null
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/SecretUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.common;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class SecretUtilsTest {
+
+ @ParameterizedTest
+ @ValueSource (strings = {"test secret", ""})
+ public void testReadSecret(final String secretTxt) throws Exception {
+ final Path secretFile = createSecretFile(secretTxt);
+
+ final char[] secret = SecretUtils.readSecret(secretFile.toString());
+ assertEquals(secretTxt, String.valueOf(secret));
+ }
+
+ @Test
+ public void tesReadSecret_withLineSeparator() throws Exception {
+ final String secretTxt = "test secret with line separator" +
System.lineSeparator();
+ final Path secretFile = createSecretFile(secretTxt);
+
+ final char[] secret = SecretUtils.readSecret(secretFile.toString());
+ assertEquals(secretTxt.substring(0, secretTxt.length() - 1),
String.valueOf(secret));
+ }
+
+ @Test
+ public void testReadSecret_fileNotExist() {
+ final String pathToFile = "NonExistingFile";
+ final IllegalStateException exception =
+ assertThrows(IllegalStateException.class, () ->
SecretUtils.readSecret(pathToFile));
+ assertEquals("Exception occurred while reading secret from file " +
pathToFile, exception.getMessage());
+ }
+
+ public static Path createSecretFile(final String secretTxt) throws
IOException {
+ final Path path = Files.createTempFile("test_", ".secrete");
+
+ final BufferedWriter writer = new BufferedWriter(new
FileWriter(path.toString()));
+ writer.append(secretTxt);
+ writer.close();
+
+ path.toFile().deleteOnExit();
+ return path;
+ }
+}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
index 3238026..f672bf4 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
@@ -381,9 +381,11 @@ public class X509TestContext {
public void clearSystemProperties(X509Util x509Util) {
System.clearProperty(x509Util.getSslKeystoreLocationProperty());
System.clearProperty(x509Util.getSslKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslKeystorePasswdPathProperty());
System.clearProperty(x509Util.getSslKeystoreTypeProperty());
System.clearProperty(x509Util.getSslTruststoreLocationProperty());
System.clearProperty(x509Util.getSslTruststorePasswdProperty());
+ System.clearProperty(x509Util.getSslTruststorePasswdPathProperty());
System.clearProperty(x509Util.getSslTruststoreTypeProperty());
System.clearProperty(x509Util.getSslHostnameVerificationEnabledProperty());
}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
index ad5eef8..c6812ae 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
@@ -28,6 +28,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.concurrent.Callable;
@@ -146,6 +147,57 @@ public class X509UtilTest extends
BaseX509ParameterizedTestCase {
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
+ public void testCreateSSLContext_withKeyStorePasswordFromFile(final
X509KeyType caKeyType,
+ final
X509KeyType certKeyType,
+ final String
keyPassword,
+ final Integer
paramIndex) throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+
+ testCreateSSLContext_withPasswordFromFile(keyPassword,
+ x509Util.getSslKeystorePasswdProperty(),
+ x509Util.getSslKeystorePasswdPathProperty());
+ }
+
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testCreateSSLContext_withTrustStorePasswordFromFile(final
X509KeyType caKeyType,
+ final
X509KeyType certKeyType,
+ final
String keyPassword,
+ final
Integer paramIndex) throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+
+ testCreateSSLContext_withPasswordFromFile(keyPassword,
+ x509Util.getSslTruststorePasswdProperty(),
+ x509Util.getSslTruststorePasswdPathProperty());
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testCreateSSLContext_withWrongKeyStorePasswordFromFile(final
X509KeyType caKeyType,
+ final
X509KeyType certKeyType,
+ final
String keyPassword,
+ final
Integer paramIndex) throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ testCreateSSLContext_withWrongPasswordFromFile(keyPassword,
x509Util.getSslKeystorePasswdPathProperty());
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testCreateSSLContext_withWrongTrustStorePasswordFromFile(final
X509KeyType caKeyType,
+ final
X509KeyType certKeyType,
+ final
String keyPassword,
+ final
Integer paramIndex) throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ testCreateSSLContext_withWrongPasswordFromFile(keyPassword,
x509Util.getSslTruststorePasswdPathProperty());
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
public void testCreateSSLContextWithCustomCipherSuites(
X509KeyType caKeyType, X509KeyType certKeyType, String
keyPassword, Integer paramIndex)
throws Exception {
@@ -834,4 +886,26 @@ public class X509UtilTest extends
BaseX509ParameterizedTestCase {
}
+ private void testCreateSSLContext_withPasswordFromFile(final String
keyPassword,
+ final String
propertyName,
+ final String
pathPropertyName) throws Exception {
+
+ final Path secretFile = SecretUtilsTest.createSecretFile(keyPassword);
+
+ System.clearProperty(propertyName);
+ System.setProperty(pathPropertyName, secretFile.toString());
+
+ x509Util.getDefaultSSLContext();
+ }
+
+ private void testCreateSSLContext_withWrongPasswordFromFile(final String
keyPassword,
+ final String
pathPropertyName) throws Exception {
+
+ final Path secretFile = SecretUtilsTest.createSecretFile(keyPassword +
"_wrong");
+
+ assertThrows(X509Exception.SSLContextException.class, () -> {
+ System.setProperty(pathPropertyName, secretFile.toString());
+ x509Util.getDefaultSSLContext();
+ });
+ }
}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
index 9cafebb..c269e19 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/admin/JettyAdminServerTest.java
@@ -19,6 +19,7 @@
package org.apache.zookeeper.server.admin;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.BufferedReader;
@@ -28,6 +29,7 @@ import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
+import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.Security;
import java.security.cert.X509Certificate;
@@ -40,6 +42,7 @@ import javax.net.ssl.X509TrustManager;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.ZKTestCase;
import org.apache.zookeeper.common.KeyStoreFileType;
+import org.apache.zookeeper.common.SecretUtilsTest;
import org.apache.zookeeper.common.X509Exception.SSLContextException;
import org.apache.zookeeper.common.X509KeyType;
import org.apache.zookeeper.common.X509TestContext;
@@ -140,9 +143,11 @@ public class JettyAdminServerTest extends ZKTestCase {
System.clearProperty("zookeeper.ssl.quorum.keyStore.location");
System.clearProperty("zookeeper.ssl.quorum.keyStore.password");
+ System.clearProperty("zookeeper.ssl.quorum.keyStore.passwordPath");
System.clearProperty("zookeeper.ssl.quorum.keyStore.type");
System.clearProperty("zookeeper.ssl.quorum.trustStore.location");
System.clearProperty("zookeeper.ssl.quorum.trustStore.password");
+ System.clearProperty("zookeeper.ssl.quorum.trustStore.passwordPath");
System.clearProperty("zookeeper.ssl.quorum.trustStore.type");
System.clearProperty("zookeeper.admin.portUnification");
System.clearProperty("zookeeper.admin.forceHttps");
@@ -247,6 +252,16 @@ public class JettyAdminServerTest extends ZKTestCase {
testForceHttps(false);
}
+ @Test
+ public void testForceHttps_withWrongPasswordFromFile() throws Exception {
+ final Path secretFile = SecretUtilsTest.createSecretFile("" + "wrong");
+
+ System.setProperty("zookeeper.ssl.quorum.keyStore.passwordPath",
secretFile.toString());
+ System.setProperty("zookeeper.ssl.quorum.trustStore.passwordPath",
secretFile.toString());
+
+ assertThrows(IOException.class, () -> testForceHttps(false));
+ }
+
private void testForceHttps(boolean portUnification) throws Exception {
System.setProperty("zookeeper.admin.forceHttps", "true");
System.setProperty("zookeeper.admin.portUnification",
String.valueOf(portUnification));
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
index ec8465c..b5af84f 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
@@ -35,6 +35,7 @@ import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
+import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
@@ -59,6 +60,7 @@ import javax.net.ssl.SSLServerSocketFactory;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.common.QuorumX509Util;
+import org.apache.zookeeper.common.SecretUtilsTest;
import org.apache.zookeeper.server.ServerCnxnFactory;
import org.apache.zookeeper.test.ClientBase;
import org.bouncycastle.asn1.ocsp.OCSPResponse;
@@ -465,8 +467,10 @@ public class QuorumSSLTest extends QuorumPeerTestBase {
private void clearSSLSystemProperties() {
System.clearProperty(quorumX509Util.getSslKeystoreLocationProperty());
System.clearProperty(quorumX509Util.getSslKeystorePasswdProperty());
+
System.clearProperty(quorumX509Util.getSslKeystorePasswdPathProperty());
System.clearProperty(quorumX509Util.getSslTruststoreLocationProperty());
System.clearProperty(quorumX509Util.getSslTruststorePasswdProperty());
+
System.clearProperty(quorumX509Util.getSslTruststorePasswdPathProperty());
System.clearProperty(quorumX509Util.getSslHostnameVerificationEnabledProperty());
System.clearProperty(quorumX509Util.getSslOcspEnabledProperty());
System.clearProperty(quorumX509Util.getSslCrlEnabledProperty());
@@ -495,6 +499,29 @@ public class QuorumSSLTest extends QuorumPeerTestBase {
assertFalse(ClientBase.waitForServerUp("127.0.0.1:" + clientPortQp3,
CONNECTION_TIMEOUT));
}
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.MINUTES)
+ public void testQuorumSSL_withPasswordFromFile() throws Exception {
+ final Path secretFile =
SecretUtilsTest.createSecretFile(String.valueOf(PASSWORD));
+
+ System.clearProperty(quorumX509Util.getSslKeystorePasswdProperty());
+ System.setProperty(quorumX509Util.getSslKeystorePasswdPathProperty(),
secretFile.toString());
+
+ System.clearProperty(quorumX509Util.getSslTruststorePasswdProperty());
+
System.setProperty(quorumX509Util.getSslTruststorePasswdPathProperty(),
secretFile.toString());
+
+ q1 = new MainThread(1, clientPortQp1, quorumConfiguration,
SSL_QUORUM_ENABLED);
+ q2 = new MainThread(2, clientPortQp2, quorumConfiguration,
SSL_QUORUM_ENABLED);
+ q3 = new MainThread(3, clientPortQp3, quorumConfiguration,
SSL_QUORUM_ENABLED);
+
+ q1.start();
+ q2.start();
+ q3.start();
+
+ assertTrue(ClientBase.waitForServerUp("127.0.0.1:" + clientPortQp1,
CONNECTION_TIMEOUT));
+ assertTrue(ClientBase.waitForServerUp("127.0.0.1:" + clientPortQp2,
CONNECTION_TIMEOUT));
+ assertTrue(ClientBase.waitForServerUp("127.0.0.1:" + clientPortQp3,
CONNECTION_TIMEOUT));
+ }
@Test
@Timeout(value = 5, unit = TimeUnit.MINUTES)
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java
index 3996238..43878e0 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientSSLTest.java
@@ -27,12 +27,14 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.nio.file.Path;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.common.ClientX509Util;
+import org.apache.zookeeper.common.SecretUtilsTest;
import org.apache.zookeeper.server.NettyServerCnxnFactory;
import org.apache.zookeeper.server.ServerCnxnFactory;
import org.apache.zookeeper.server.auth.ProviderRegistry;
@@ -67,8 +69,10 @@ public class ClientSSLTest extends QuorumPeerTestBase {
System.clearProperty(ZKClientConfig.SECURE_CLIENT);
System.clearProperty(clientX509Util.getSslKeystoreLocationProperty());
System.clearProperty(clientX509Util.getSslKeystorePasswdProperty());
+
System.clearProperty(clientX509Util.getSslKeystorePasswdPathProperty());
System.clearProperty(clientX509Util.getSslTruststoreLocationProperty());
System.clearProperty(clientX509Util.getSslTruststorePasswdProperty());
+
System.clearProperty(clientX509Util.getSslTruststorePasswdPathProperty());
clientX509Util.close();
}
@@ -110,6 +114,19 @@ public class ClientSSLTest extends QuorumPeerTestBase {
testClientServerSSL(true);
}
+ @Test
+ public void testClientServerSSL_withPasswordFromFile() throws Exception {
+ final Path secretFile = SecretUtilsTest.createSecretFile("testpass");
+
+ System.clearProperty(clientX509Util.getSslKeystorePasswdProperty());
+ System.setProperty(clientX509Util.getSslKeystorePasswdPathProperty(),
secretFile.toString());
+
+ System.clearProperty(clientX509Util.getSslTruststorePasswdProperty());
+
System.setProperty(clientX509Util.getSslTruststorePasswdPathProperty(),
secretFile.toString());
+
+ testClientServerSSL(true);
+ }
+
public void testClientServerSSL(boolean useSecurePort) throws Exception {
final int SERVER_COUNT = 3;
final int[] clientPorts = new int[SERVER_COUNT];