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

starocean999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 66f237a9fbb [fix](config) Mask sensitive FE configs in SHOW FRONTEND 
CONFIG via @ConfField(sensitive) (#67338)
66f237a9fbb is described below

commit 66f237a9fbbc8048cb7c69a7a11829f722857aa0
Author: starocean999 <[email protected]>
AuthorDate: Mon Sep 7 10:02:59 2026 +0800

    [fix](config) Mask sensitive FE configs in SHOW FRONTEND CONFIG via 
@ConfField(sensitive) (#67338)
---
 .../main/java/org/apache/doris/common/Config.java  |  11 +-
 .../plans/commands/ShowConfigCommandTest.java      | 148 +++++++++++++++++++++
 2 files changed, 154 insertions(+), 5 deletions(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index a56912a77cd..15d8c3b90f5 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -362,7 +362,7 @@ public class Config extends ConfigBase {
     @ConfField(description = "Path to the FE TLS private key.")
     public static String tls_private_key_path = "";
 
-    @ConfField(description = "Password for the FE TLS private key.")
+    @ConfField(sensitive = true, description = "Password for the FE TLS 
private key.")
     public static String tls_private_key_password = "";
 
     @ConfField(description = "Path to the FE TLS CA certificate.")
@@ -393,7 +393,7 @@ public class Config extends ConfigBase {
     public static String key_store_path =  EnvUtils.getDorisHome()
             + "/conf/ssl/doris_ssl_certificate.keystore";
 
-    @ConfField(description = "The key store password of FE https service")
+    @ConfField(sensitive = true, description = "The key store password of FE 
https service")
     public static String key_store_password = "";
 
     @ConfField(description = "The key store type of FE https service")
@@ -2376,13 +2376,13 @@ public class Config extends ConfigBase {
     /**
      * Password for default CA certificate file.
      */
-    @ConfField(mutable = false, masterOnly = false)
+    @ConfField(sensitive = true, mutable = false, masterOnly = false)
     public static String mysql_ssl_default_ca_certificate_password = "doris";
 
     /**
      * Password for default CA certificate file.
      */
-    @ConfField(mutable = false, masterOnly = false)
+    @ConfField(sensitive = true, mutable = false, masterOnly = false)
     public static String mysql_ssl_default_server_certificate_password = 
"doris";
 
     /**
@@ -2774,7 +2774,8 @@ public class Config extends ConfigBase {
             + "BE in partition rebalance mode. If it is less than " + "this 
value, it will be diagnosed as balanced.")
     public static double diagnose_balance_max_tablet_num_ratio = 1.1;
 
-    @ConfField(masterOnly = true, description = "Set root user initial 
2-staged SHA-1 encrypted password, default as "
+    @ConfField(sensitive = true, masterOnly = true, description = "Set root 
user initial 2-staged SHA-1 "
+            + "encrypted password, default as "
             + "'', means no root password. Subsequent `set password` 
operations for "
             + "root user will overwrite the initial root password. Example: If 
you "
             + "want to configure a plaintext password `root@123`.You can 
execute "
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowConfigCommandTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowConfigCommandTest.java
new file mode 100644
index 00000000000..8856d7cd846
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowConfigCommandTest.java
@@ -0,0 +1,148 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
+import org.apache.doris.common.ConfigBase;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.qe.ShowResultSet;
+import org.apache.doris.qe.StmtExecutor;
+import org.apache.doris.system.NodeType;
+import org.apache.doris.utframe.TestWithFeService;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Test for ShowConfigCommand, especially the masking of sensitive config 
values.
+ */
+public class ShowConfigCommandTest extends TestWithFeService {
+
+    private static final String MASK_VALUE = ConfigBase.SENSITIVE_CONF_MASK;
+
+    private static final List<String> SENSITIVE_KEYS = List.of(
+            "mysql_ssl_default_server_certificate_password",
+            "key_store_password",
+            "tls_private_key_password",
+            "initial_root_password",
+            "mysql_ssl_default_ca_certificate_password");
+
+    /**
+     * Expected value shown by {@code SHOW FRONTEND CONFIG} for a config: 
ConfigBase masks a
+     * sensitive (non-empty) value, while an empty sensitive value is left 
as-is.
+     */
+    private static String expectedValue(String key) throws Exception {
+        Field field = Config.class.getField(key);
+        String rawValue = ConfigBase.getConfValue(field);
+        return rawValue.isEmpty() ? rawValue : MASK_VALUE;
+    }
+
+    private ShowResultSet runShowFrontendConfig(String pattern) throws 
Exception {
+        ShowConfigCommand command = new ShowConfigCommand(NodeType.FRONTEND);
+        if (pattern != null) {
+            command.setPattern(pattern);
+        }
+        StmtExecutor executor = Mockito.mock(StmtExecutor.class);
+        ArgumentCaptor<ShowResultSet> captor = 
ArgumentCaptor.forClass(ShowResultSet.class);
+        command.run(connectContext, executor);
+        Mockito.verify(executor).sendResultSet(captor.capture());
+        return captor.getValue();
+    }
+
+    @Test
+    public void testParseShowConfig() {
+        LogicalPlan plan = new NereidsParser().parseSingle("show frontend 
config");
+        Assertions.assertTrue(plan instanceof ShowConfigCommand);
+        plan = new NereidsParser().parseSingle("show backend config");
+        Assertions.assertTrue(plan instanceof ShowConfigCommand);
+    }
+
+    @Test
+    public void testMaskSensitiveConfig() throws Exception {
+        ShowResultSet resultSet = runShowFrontendConfig(null);
+        List<List<String>> rows = resultSet.getResultRows();
+        Assertions.assertFalse(rows.isEmpty());
+
+        Map<String, List<String>> keyToRow = rows.stream()
+                .collect(Collectors.toMap(row -> row.get(0), row -> row));
+
+        for (String sensitiveKey : SENSITIVE_KEYS) {
+            Assertions.assertTrue(keyToRow.containsKey(sensitiveKey),
+                    "config '" + sensitiveKey + "' should be present in show 
frontend config");
+            List<String> row = keyToRow.get(sensitiveKey);
+            Assertions.assertEquals(ShowConfigCommand.FE_TITLE_NAMES.size(), 
row.size());
+            Assertions.assertEquals(expectedValue(sensitiveKey), row.get(1),
+                    "value of config '" + sensitiveKey + "' should be masked 
with " + MASK_VALUE
+                            + " if set, otherwise left empty");
+            // Only the value column is masked, the other columns stay intact.
+            Assertions.assertEquals("String", row.get(2));
+        }
+
+        // A normal config keeps its real value and is never masked.
+        Assertions.assertTrue(keyToRow.containsKey("http_port"));
+        
Assertions.assertEquals(ConfigBase.getConfValue(Config.class.getField("http_port")),
+                keyToRow.get("http_port").get(1));
+        Assertions.assertNotEquals(MASK_VALUE, 
keyToRow.get("http_port").get(1));
+    }
+
+    @Test
+    public void testMaskSensitiveConfigWithPattern() throws Exception {
+        ShowResultSet resultSet = 
runShowFrontendConfig("mysql_ssl_default_ca_certificate_password");
+        List<List<String>> rows = resultSet.getResultRows();
+        Assertions.assertEquals(1, rows.size());
+        Assertions.assertEquals("mysql_ssl_default_ca_certificate_password", 
rows.get(0).get(0));
+        
Assertions.assertEquals(expectedValue("mysql_ssl_default_ca_certificate_password"),
 rows.get(0).get(1));
+    }
+
+    @Test
+    public void testMaskAllPasswordConfigsWithPattern() throws Exception {
+        ShowResultSet resultSet = runShowFrontendConfig("%password%");
+        List<List<String>> rows = resultSet.getResultRows();
+        // The pattern '%password%' matches 6 configs: the 5 sensitive ones 
plus
+        // tls_cert_based_auth_ignore_password which is not a secret.
+        Assertions.assertEquals(SENSITIVE_KEYS.size() + 1, rows.size());
+        Map<String, String> keyToValue = rows.stream()
+                .collect(Collectors.toMap(row -> row.get(0), row -> 
row.get(1)));
+        for (String sensitiveKey : SENSITIVE_KEYS) {
+            Assertions.assertEquals(expectedValue(sensitiveKey), 
keyToValue.get(sensitiveKey),
+                    "value of config '" + sensitiveKey + "' should be masked 
with " + MASK_VALUE
+                            + " if set, otherwise left empty");
+        }
+        // A non-sensitive config containing "password" in its name keeps its 
real value.
+        Assertions.assertEquals("false", 
keyToValue.get("tls_cert_based_auth_ignore_password"));
+    }
+
+    @Test
+    public void testShowBackendConfigNotExists() throws Exception {
+        ShowConfigCommand command = new ShowConfigCommand(NodeType.BACKEND);
+        command.setBackendId(99999L);
+        StmtExecutor executor = Mockito.mock(StmtExecutor.class);
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> command.run(connectContext, executor));
+        Assertions.assertEquals("errCode = 2, detailMessage = Backend 99999 
not exists", exception.getMessage());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to