This is an automated email from the ASF dual-hosted git repository.
luwei16 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 6341cd0d182 [refactor](tde) Add local root key metadata for TDE
(#66948)
6341cd0d182 is described below
commit 6341cd0d182c1e3814cbfd152e0bf79129ecb31c
Author: Luwei <[email protected]>
AuthorDate: Mon Aug 24 17:48:28 2026 +0800
[refactor](tde) Add local root key metadata for TDE (#66948)
### What problem does this PR solve?
Issue Number: None
Problem Summary: TDE root-key metadata currently cannot represent and
validate a file-based local root key across configuration, edit-log
serialization, and key rotation. Add a persisted local key file path and
SHA-256 hash, keep the Base64 key in memory only, expose the
corresponding FE configuration and rotation properties, and cover
copying, hash verification, JSON/edit-log serialization, and
sensitive-field exclusion.
### Release note
None
### Check List (For Author)
- Test: Unit Test
- ./run-fe-ut.sh --run org.apache.doris.encryption.RootKeyInfoTest (5
tests passed)
- Behavior changed: No. Existing root-key loading behavior remains
unchanged; this adds metadata and configuration support for file-based
local root keys.
- Does this need documentation: No
---
.../main/java/org/apache/doris/common/Config.java | 7 +-
.../org/apache/doris/encryption/RootKeyInfo.java | 40 ++++++++++
.../commands/AdminRotateTdeRootKeyCommand.java | 6 ++
.../apache/doris/encryption/RootKeyInfoTest.java | 93 ++++++++++++++++++++++
4 files changed, 145 insertions(+), 1 deletion(-)
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 6ba1e477efc..b8c205cf041 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
@@ -3584,9 +3584,14 @@ public class Config extends ConfigBase {
public static String doris_tde_key_region = "";
@ConfField(mutable = true, description = "The key provider identifier for
TDE (Transparent Data Encryption). "
- + "Recognized values include aws_kms, aliyun_kms, ranger_kms,
gcp_kms, and azure_kms.")
+ + "Recognized values include aws_kms, aliyun_kms, ranger_kms,
gcp_kms, azure_kms, and local. "
+ + "For local mode, doris_tde_root_key_file must be set to a key
file path.")
public static String doris_tde_key_provider = "";
+ @ConfField(description = "Path to the root key file for TDE local mode.
The file content must be a "
+ + "Base64-encoded key.")
+ public static String doris_tde_root_key_file = "";
+
@ConfField(mutable = true, description = "The simple authentication user
name for TDE Hadoop KMS")
public static String doris_tde_hadoop_user_name = "hadoop";
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
b/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
index d282e2a3eab..be1181186a2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
@@ -19,6 +19,8 @@ package org.apache.doris.encryption;
import com.google.gson.annotations.SerializedName;
+import java.security.MessageDigest;
+import java.util.Base64;
import java.util.Objects;
public class RootKeyInfo {
@@ -71,6 +73,9 @@ public class RootKeyInfo {
this.ak = info.ak;
this.sk = info.sk;
this.password = info.password;
+ this.rootKeyFilePath = info.rootKeyFilePath;
+ this.rootKeyHash = info.rootKeyHash;
+ this.rootKeyBase64 = info.rootKeyBase64;
}
@SerializedName(value = "type")
@@ -96,4 +101,39 @@ public class RootKeyInfo {
@SerializedName(value = "password")
public String password;
+
+ // File path of the local root key. This is a persisted key reference,
similar to cmkId for KMS.
+ @SerializedName(value = "rootKeyFilePath")
+ public String rootKeyFilePath;
+
+ // SHA-256 hash of the root key (Base64 encoded). Used to verify the root
key on FE restart.
+ @SerializedName(value = "rootKeyHash")
+ public String rootKeyHash;
+
+ // Root key in Base64 encoding. Used for passing the key during rotation,
not persisted to the edit log.
+ public transient String rootKeyBase64;
+
+ public void setRootKeyHashFromKey(byte[] rootKeyBytes) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(rootKeyBytes);
+ this.rootKeyHash = Base64.getEncoder().encodeToString(hash);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to compute root key hash", e);
+ }
+ }
+
+ public boolean verifyRootKey(byte[] rootKeyBytes) {
+ if (this.rootKeyHash == null || this.rootKeyHash.isEmpty()) {
+ return true;
+ }
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(rootKeyBytes);
+ String inputHash = Base64.getEncoder().encodeToString(hash);
+ return this.rootKeyHash.equals(inputHash);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to verify root key", e);
+ }
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
index 469c871f3a9..e9b75c087e8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
@@ -47,6 +47,12 @@ public class AdminRotateTdeRootKeyCommand extends Command
implements ForwardWith
public static final String DORIS_TDE_KEY_REGION = "doris_tde_key_region";
+ // File path containing the new key for local key rotation.
+ public static final String DORIS_TDE_KEY_NEW_KEY_FILE =
"doris_tde_key_new_key_file";
+
+ // File path containing the original key for verification during local key
rotation.
+ public static final String DORIS_TDE_KEY_ORIGINAL_KEY_FILE =
"doris_tde_key_original_key_file";
+
private final Map<String, String> properties;
public AdminRotateTdeRootKeyCommand(Map<String, String> properties) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
b/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
index fd9dd89423d..4627eb9c385 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
@@ -17,11 +17,21 @@
package org.apache.doris.encryption;
+import org.apache.doris.encryption.EncryptionKey.Algorithm;
+import org.apache.doris.encryption.EncryptionKey.KeyType;
+import org.apache.doris.encryption.RootKeyInfo.RootKeyType;
import org.apache.doris.persist.gson.GsonUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
public class RootKeyInfoTest {
@Test
public void testDeserializeGcpAndAzureKmsTypes() {
@@ -31,4 +41,87 @@ public class RootKeyInfoTest {
RootKeyInfo azureInfo =
GsonUtils.GSON.fromJson("{\"type\":\"AZURE_KMS\"}", RootKeyInfo.class);
Assertions.assertEquals(RootKeyInfo.RootKeyType.AZURE_KMS,
azureInfo.type);
}
+
+ @Test
+ public void testLocalRootKeyInfoSerialization() {
+ RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+ String json = GsonUtils.GSON.toJson(rootKeyInfo);
+ Assertions.assertTrue(json.contains("rootKeyFilePath"));
+ Assertions.assertTrue(json.contains("rootKeyHash"));
+ Assertions.assertFalse(json.contains("rootKeyBase64"));
+ Assertions.assertFalse(json.contains(rootKeyInfo.rootKeyBase64));
+
+ RootKeyInfo restored = GsonUtils.GSON.fromJson(json,
RootKeyInfo.class);
+ Assertions.assertEquals(RootKeyType.LOCAL, restored.type);
+ Assertions.assertEquals(rootKeyInfo.rootKeyFilePath,
restored.rootKeyFilePath);
+ Assertions.assertEquals(rootKeyInfo.rootKeyHash, restored.rootKeyHash);
+ Assertions.assertNull(restored.rootKeyBase64);
+ }
+
+ @Test
+ public void testLocalRootKeyHashVerification() {
+ byte[] rootKey = "local-root-key".getBytes(StandardCharsets.UTF_8);
+ RootKeyInfo rootKeyInfo = new RootKeyInfo();
+
+ Assertions.assertTrue(rootKeyInfo.verifyRootKey(rootKey));
+
+ rootKeyInfo.setRootKeyHashFromKey(rootKey);
+ Assertions.assertTrue(rootKeyInfo.verifyRootKey(rootKey));
+
Assertions.assertFalse(rootKeyInfo.verifyRootKey("different-key".getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void testCopyLocalRootKeyInfo() {
+ RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+ RootKeyInfo copied = new RootKeyInfo(rootKeyInfo);
+
+ Assertions.assertEquals(rootKeyInfo.rootKeyFilePath,
copied.rootKeyFilePath);
+ Assertions.assertEquals(rootKeyInfo.rootKeyHash, copied.rootKeyHash);
+ Assertions.assertEquals(rootKeyInfo.rootKeyBase64,
copied.rootKeyBase64);
+ }
+
+ @Test
+ public void testKeyManagerStoreSerializationKeepsLocalRootKeyReference()
throws Exception {
+ RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+ EncryptionKey masterKey = new EncryptionKey();
+ masterKey.id = "1";
+ masterKey.version = 1;
+ masterKey.parentId = "local";
+ masterKey.parentVersion = 1;
+ masterKey.type = KeyType.MASTER_KEY;
+ masterKey.algorithm = Algorithm.AES256;
+ masterKey.ciphertext = "ciphertext";
+ masterKey.crc = 1234;
+
+ KeyManagerStore store = new KeyManagerStore();
+ store.setRootKeyInfo(rootKeyInfo);
+ store.addMasterKey(masterKey);
+
+ ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+ store.write(new DataOutputStream(byteOutput));
+
+ KeyManagerStore restored = KeyManagerStore.read(new DataInputStream(
+ new ByteArrayInputStream(byteOutput.toByteArray())));
+
+ Assertions.assertEquals(RootKeyType.LOCAL,
restored.getRootKeyInfo().type);
+ Assertions.assertEquals(rootKeyInfo.rootKeyFilePath,
restored.getRootKeyInfo().rootKeyFilePath);
+ Assertions.assertEquals(rootKeyInfo.rootKeyHash,
restored.getRootKeyInfo().rootKeyHash);
+ Assertions.assertNull(restored.getRootKeyInfo().rootKeyBase64);
+ Assertions.assertEquals(1, restored.getMasterKeys().size());
+ Assertions.assertEquals(masterKey.ciphertext,
restored.getMasterKeys().get(0).ciphertext);
+ }
+
+ private RootKeyInfo createLocalRootKeyInfo() {
+ RootKeyInfo rootKeyInfo = new RootKeyInfo();
+ rootKeyInfo.type = RootKeyType.LOCAL;
+ rootKeyInfo.cmkId = "local";
+ rootKeyInfo.rootKeyFilePath =
"/opt/apache-doris/fe/conf/doris_tde_root_key";
+ rootKeyInfo.rootKeyHash = "hash";
+ rootKeyInfo.rootKeyBase64 = Base64.getEncoder().encodeToString(
+ "secret".getBytes(StandardCharsets.UTF_8));
+ return rootKeyInfo;
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]