This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 924e51b2eab [fix](config) Mask sensitive FE configs in SHOW FRONTEND
CONFIG via @ConfField(sensitive) (#67338) (#67920)
924e51b2eab is described below
commit 924e51b2eab5e581e21ee75fd257758ec4653f0d
Author: starocean999 <[email protected]>
AuthorDate: Tue Sep 15 11:47:44 2026 +0800
[fix](config) Mask sensitive FE configs in SHOW FRONTEND CONFIG via
@ConfField(sensitive) (#67338) (#67920)
pick https://github.com/apache/doris/pull/67338
### What problem does this PR solve?
Issue Number: close #xxx
Related PR: #xxx
Problem Summary:
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
.../main/java/org/apache/doris/common/Config.java | 26 ++--
.../plans/commands/ShowConfigCommandTest.java | 150 +++++++++++++++++++++
2 files changed, 161 insertions(+), 15 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 90104411276..55a531c054b 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
@@ -435,7 +435,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."})
@@ -469,8 +469,7 @@ public class Config extends ConfigBase {
public static String key_store_path = EnvUtils.getDorisHome()
+ "/conf/ssl/doris_ssl_certificate.keystore";
- @ConfField(description = {"FE https 服务的 key store 密码",
- "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 = {"FE https 服务的 key store 类型",
@@ -2683,13 +2682,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";
/**
@@ -3198,16 +3197,13 @@ public class Config extends ConfigBase {
})
public static double diagnose_balance_max_tablet_num_ratio = 1.1;
- @ConfField(masterOnly = true, description = {
- "设置 root 用户初始化 2 阶段 SHA-1 加密密码,默认为'',即不设置 root 密码。"
- + "后续 root 用户的 `set password` 操作会将 root 初始化密码覆盖。"
- + "示例:如要配置密码的明文是 `root@123`,可在 Doris 执行 SQL `select
password('root@123')` "
- + "获取加密密码 `*A00C34073A26B40AB4307650BFB9309D6BFA6999`",
- "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 Doris SQL `select password('root@123')`
to generate encrypted "
- + "password `*A00C34073A26B40AB4307650BFB9309D6BFA6999`"})
+ @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 "
+ + "Doris SQL `select password('root@123')` to generate encrypted "
+ + "password `*A00C34073A26B40AB4307650BFB9309D6BFA6999`")
public static String initial_root_password = "";
@ConfField(description = {"nereids trace 文件的存放路径。",
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..714a531aa26
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowConfigCommandTest.java
@@ -0,0 +1,150 @@
+// 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.Arrays;
+import java.util.Collections;
+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 =
Collections.unmodifiableList(Arrays.asList(
+ "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]