This is an automated email from the ASF dual-hosted git repository.

andor 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 f61fdb1d1 Add log redactor method when logging ZK config properties
f61fdb1d1 is described below

commit f61fdb1d1cfa67b5233a193844362bb8dfdaa9c0
Author: Andor Molnar <[email protected]>
AuthorDate: Fri Dec 5 16:16:39 2025 -0600

    Add log redactor method when logging ZK config properties
---
 .../java/org/apache/zookeeper/common/ZKConfig.java | 17 ++++-
 .../org/apache/zookeeper/common/ZKConfigTest.java  | 72 +++++++++++++++++++++-
 .../org/apache/zookeeper/test/LoggerTestTool.java  | 30 ++++++++-
 .../src/test/resources/zookeeper-client.config     |  5 ++
 4 files changed, 120 insertions(+), 4 deletions(-)

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 f6221eeb7..fa8cd5459 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
@@ -23,9 +23,11 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
+
 import org.apache.zookeeper.Environment;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
 import org.apache.zookeeper.server.util.VerifyingFileFactory;
@@ -93,6 +95,10 @@ public ZKConfig(String configPath) throws 
QuorumPeerConfig.ConfigException {
     public ZKConfig(File configFile) throws QuorumPeerConfig.ConfigException {
         this();
         addConfiguration(configFile);
+        Map<String, String> p = new HashMap<>();
+        for (Entry<String, String> entry : properties.entrySet()) {
+            p.put(entry.getKey(), logRedactor(entry.getKey(), 
entry.getValue()));
+        }
         LOG.info("ZK Config {}", this.properties);
     }
 
@@ -206,7 +212,7 @@ public void setProperty(String key, String value) {
         }
         String oldValue = properties.put(key, value);
         if (null != oldValue && !oldValue.equals(value)) {
-            LOG.debug("key {}'s value {} is replaced with new value {}", key, 
oldValue, value);
+            LOG.debug("key {}'s value {} is replaced with new value {}", key, 
logRedactor(key, oldValue), logRedactor(key, value));
         }
     }
 
@@ -330,4 +336,13 @@ public int getInt(String key, int defaultValue) {
         return defaultValue;
     }
 
+    private String logRedactor(String key, String value) {
+        if (key == null) {
+            return value;
+        }
+        if (key.toLowerCase(Locale.ROOT).endsWith("password")) {
+            return "***";
+        }
+        return value;
+    }
 }
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ZKConfigTest.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ZKConfigTest.java
index c64d5e694..7b87e2e3e 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ZKConfigTest.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ZKConfigTest.java
@@ -19,13 +19,35 @@
 package org.apache.zookeeper.common;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.zookeeper.test.LoggerTestTool;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import ch.qos.logback.classic.Level;
+
+import java.io.File;
+import java.io.IOException;
 
 public class ZKConfigTest {
 
-    X509Util x509Util = new ClientX509Util();
+    private final X509Util x509Util = new ClientX509Util();
+    private static LoggerTestTool loggerTestTool;
+
+    @BeforeAll
+    public static void setUpBeforeClass() {
+        loggerTestTool = new LoggerTestTool(ZKConfig.class, Level.DEBUG);
+    }
+
+    @AfterAll
+    public static void tearDownAfterClass() throws Exception {
+        loggerTestTool.close();
+    }
 
     @AfterEach
     public void tearDown() throws Exception {
@@ -90,4 +112,52 @@ public void 
testBooleanRetrievalFromPropertyWithWhitespacesAtBeginningAndEnd() {
         boolean result = conf.getBoolean(x509Util.getSslProtocolProperty(), 
defaultValue);
         assertEquals(value, result);
     }
+
+    @Test
+    public void testLogRedactorFromConfigFile() throws ConfigException, 
IOException {
+        // Arrange
+        File configFile = new 
File("./src/test/resources/zookeeper-client.config");
+
+        // Act
+        new ZKConfig(configFile.toPath());
+
+        // Assert
+        String logLine = loggerTestTool.readLogLine("ZK Config");
+        assertNotNull(logLine, "Unable to find ZK Config line in the logs");
+        assertFalse(logLine.contains("FileSecret456!"), "Logs should not 
contain any secrets");
+        assertFalse(logLine.contains("AnotherFileSecret789!"), "Logs should 
not contain any secrets");
+        assertTrue(logLine.contains("/home/zookeeper/top_secret.txt"));      
// what we shouldn't redact
+    }
+
+    @Test
+    public void testLogRedactorInDebugLogs() throws IOException {
+        // Arrange
+        ZKConfig conf = new ZKConfig();
+
+        // Act
+        conf.setProperty("zookeeper.some.secret.password", "0ldP4ssw0rd");
+        conf.setProperty("zookeeper.some.secret.password", "N3Ws3cr3t");
+
+        // Assert
+        String logLine = loggerTestTool.readLogLine("replaced with new value");
+        assertNotNull(logLine, "Unable to find relevant line in the logs");
+        assertFalse(logLine.contains("0ldP4ssw0rd"), "Logs should not contain 
any secrets");
+        assertFalse(logLine.contains("N3Ws3cr3t"), "Logs should not contain 
any secrets");
+    }
+
+    @Test
+    public void testDontRedactorInDebugLogs() throws IOException {
+        // Arrange
+        ZKConfig conf = new ZKConfig();
+
+        // Act
+        conf.setProperty("zookeeper.some.secret.passwordPath", 
"/home/zookeeper/old_secret.txt");  // what we shouldn't redact
+        conf.setProperty("zookeeper.some.secret.passwordPath", 
"/home/zookeeper/new_secret.txt");  // what we shouldn't redact
+
+        // Assert
+        String logLine = loggerTestTool.readLogLine("replaced with new value");
+        assertNotNull(logLine, "Unable to find relevant line in the logs");
+        assertTrue(logLine.contains("/home/zookeeper/new_secret.txt"));
+    }
+
 }
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/LoggerTestTool.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/LoggerTestTool.java
index c95a0d32d..5f225ee18 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/LoggerTestTool.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/LoggerTestTool.java
@@ -27,17 +27,27 @@
 import ch.qos.logback.core.OutputStreamAppender;
 import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
 import org.slf4j.LoggerFactory;
 
 public class LoggerTestTool implements AutoCloseable {
   private final ByteArrayOutputStream os;
   private Appender<ILoggingEvent> appender;
   private Logger qlogger;
+  private Level logLevel = Level.INFO;
 
   public LoggerTestTool(Class<?> cls) {
     os = createLoggingStream(cls);
   }
 
+  public LoggerTestTool(Class<?> cls, Level logLevel) {
+    this.logLevel = logLevel;
+    this.os = createLoggingStream(cls);
+  }
+
   public LoggerTestTool(String cls) {
     os = createLoggingStream(cls);
   }
@@ -51,7 +61,7 @@ private ByteArrayOutputStream createLoggingStream(Class<?> 
cls) {
     appender = getConsoleAppender(os);
     qlogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(cls);
     qlogger.addAppender(appender);
-    qlogger.setLevel(Level.INFO);
+    qlogger.setLevel(logLevel);
     appender.start();
     return os;
   }
@@ -61,7 +71,7 @@ private ByteArrayOutputStream createLoggingStream(String cls) 
{
     appender = getConsoleAppender(os);
     qlogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(cls);
     qlogger.addAppender(appender);
-    qlogger.setLevel(Level.INFO);
+    qlogger.setLevel(logLevel);
     appender.start();
     return os;
   }
@@ -69,6 +79,7 @@ private ByteArrayOutputStream createLoggingStream(String cls) 
{
   private OutputStreamAppender<ILoggingEvent> 
getConsoleAppender(ByteArrayOutputStream os) {
     Logger rootLogger =
         (ch.qos.logback.classic.Logger) 
LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+    rootLogger.setLevel(logLevel);
     Layout<ILoggingEvent> layout = ((LayoutWrappingEncoder<ILoggingEvent>)
         ((OutputStreamAppender<ILoggingEvent>) 
rootLogger.getAppender("CONSOLE")).getEncoder()).getLayout();
 
@@ -80,6 +91,21 @@ private OutputStreamAppender<ILoggingEvent> 
getConsoleAppender(ByteArrayOutputSt
     return appender;
   }
 
+  public String readLogLine(String search) throws IOException {
+    try {
+      LineNumberReader r = new LineNumberReader(new 
StringReader(os.toString()));
+      String line;
+      while ((line = r.readLine()) != null) {
+        if (line.contains(search)) {
+          return line;
+        }
+      }
+      return null;
+    } finally {
+      os.reset();
+    }
+  }
+
   @Override
   public void close() throws Exception {
     qlogger.detachAppender(appender);
diff --git a/zookeeper-server/src/test/resources/zookeeper-client.config 
b/zookeeper-server/src/test/resources/zookeeper-client.config
new file mode 100644
index 000000000..096105ce9
--- /dev/null
+++ b/zookeeper-server/src/test/resources/zookeeper-client.config
@@ -0,0 +1,5 @@
+# Sample secrets
+zookeeper.ssl.keyStore.password=FileSecret456!
+zookeeper.ssl.keyStore.passwordPath=/home/zookeeper/top_secret.txt
+ssl.quorum.keyStore.password=AnotherFileSecret789!
+

Reply via email to