release(secrets-mcp): v0.3.2 — 修复 key_ref 多租户与歧义
All checks were successful
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Successful in 3m41s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s

- env_map:key_ref 解析传入 user_id;支持 folder/name;多条匹配时报错
- 文档同步 key_ref 说明
- bump secrets-mcp 0.3.1 → 0.3.2,更新 Cargo.lock

Made-with: Cursor
This commit is contained in:
2026-03-27 10:45:12 +08:00
parent beade4503d
commit 08e81363c9
5 changed files with 32 additions and 9 deletions

View File

@@ -118,7 +118,7 @@ oauth_accounts (
### PEM 共享(`key_ref` ### PEM 共享(`key_ref`
将共享 PEM 存为 **`type=key`** 的 entry其它记录在 `metadata.key_ref` 指向该 key 的 `name`。更新 key 记录后,引用方通过服务层解析合并逻辑即可使用新密钥(实现见 `secrets_core::service`)。 将共享 PEM 存为 **`type=key`** 的 entry其它记录在 `metadata.key_ref` 指向该 key 的 `name`(支持 `folder/name` 格式消歧)。更新 key 记录后,引用方通过服务层解析合并逻辑即可使用新密钥(实现见 `secrets_core::service::env_map`)。
## 代码规范 ## 代码规范

2
Cargo.lock generated
View File

@@ -1968,7 +1968,7 @@ dependencies = [
[[package]] [[package]]
name = "secrets-mcp" name = "secrets-mcp"
version = "0.3.1" version = "0.3.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"askama", "askama",

View File

@@ -147,7 +147,7 @@ flowchart LR
### PEM 共享(`key_ref` ### PEM 共享(`key_ref`
同一 PEM 可被多条 `server` 等记录引用:将 PEM 存为 **`type=key`** 的 entry在其它条目的 `metadata.key_ref` 中写该 key 条目的 `name`;轮换时只更新 key 对应记录即可。 同一 PEM 可被多条 `server` 等记录引用:将 PEM 存为 **`type=key`** 的 entry在其它条目的 `metadata.key_ref` 中写该 key 条目的 `name`(支持 `folder/name` 格式消歧);轮换时只更新 key 记录即可。
## 审计日志 ## 审计日志

View File

@@ -26,7 +26,8 @@ pub async fn build_env_map(
let mut combined: HashMap<String, String> = HashMap::new(); let mut combined: HashMap<String, String> = HashMap::new();
for entry in &entries { for entry in &entries {
let entry_map = build_entry_env_map(pool, entry, only_fields, prefix, master_key).await?; let entry_map =
build_entry_env_map(pool, entry, only_fields, prefix, master_key, user_id).await?;
combined.extend(entry_map); combined.extend(entry_map);
} }
@@ -39,6 +40,7 @@ async fn build_entry_env_map(
only_fields: &[String], only_fields: &[String],
prefix: &str, prefix: &str,
master_key: &[u8; 32], master_key: &[u8; 32],
user_id: Option<Uuid>,
) -> Result<HashMap<String, String>> { ) -> Result<HashMap<String, String>> {
let entry_ids = vec![entry.id]; let entry_ids = vec![entry.id];
let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?;
@@ -66,10 +68,31 @@ async fn build_entry_env_map(
map.insert(key, json_to_env_string(&decrypted)); map.insert(key, json_to_env_string(&decrypted));
} }
// Resolve key_ref // Resolve key_ref. Supported formats: "name" or "folder/name".
if let Some(key_ref) = entry.metadata.get("key_ref").and_then(|v| v.as_str()) { if let Some(key_ref) = entry.metadata.get("key_ref").and_then(|v| v.as_str()) {
let key_entries = let (ref_folder, ref_name) = if let Some((f, n)) = key_ref.split_once('/') {
fetch_entries(pool, None, Some("key"), Some(key_ref), &[], None, None).await?; (Some(f), n)
} else {
(None, key_ref)
};
let key_entries = fetch_entries(
pool,
ref_folder,
Some("key"),
Some(ref_name),
&[],
None,
user_id,
)
.await?;
if key_entries.len() > 1 {
anyhow::bail!(
"key_ref '{}' matched {} entries; qualify with folder/name to resolve the ambiguity",
key_ref,
key_entries.len()
);
}
if let Some(key_entry) = key_entries.first() { if let Some(key_entry) = key_entries.first() {
let key_ids = vec![key_entry.id]; let key_ids = vec![key_entry.id];
@@ -87,7 +110,7 @@ async fn build_entry_env_map(
map.insert(key_var, json_to_env_string(&decrypted)); map.insert(key_var, json_to_env_string(&decrypted));
} }
} else { } else {
tracing::warn!(key_ref, "key_ref target not found"); tracing::warn!(key_ref, ?user_id, "key_ref target not found");
} }
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "secrets-mcp" name = "secrets-mcp"
version = "0.3.1" version = "0.3.2"
edition.workspace = true edition.workspace = true
[[bin]] [[bin]]