提取 auth/api 公共变量避免重复;用 xargs 单行替换 while 循环清理 旧 assets;POST 分支用管道直接取 id 省去临时文件。 279 行 → 248 行。 Made-with: Cursor
secrets-mcp
Workspace:secrets-core + secrets-mcp(HTTP Streamable MCP + Web)。多租户密钥与元数据存 PostgreSQL;用户通过 Google OAuth 登录,API Key 鉴权 MCP 请求;秘密数据用用户密码短语派生的密钥在客户端加密,服务端不持有原始密钥。
安装
cargo build --release -p secrets-mcp
# 产物: target/release/secrets-mcp
发版产物见 Gitea Release(tag:secrets-mcp-<version>,Linux musl 预编译);其它平台本地 cargo build。
环境变量与本地运行
复制 deploy/.env.example 为项目根目录 .env(已在 .gitignore),或导出同名变量:
| 变量 | 说明 |
|---|---|
SECRETS_DATABASE_URL |
必填。PostgreSQL 连接串(建议专用库,如 secrets-mcp)。 |
BASE_URL |
对外访问基址;OAuth 回调为 {BASE_URL}/auth/google/callback。默认 http://localhost:9315。 |
SECRETS_MCP_BIND |
监听地址,默认 0.0.0.0:9315。反代时常为 127.0.0.1:9315。 |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
可选;不配置则无 Google 登录入口。运行时从环境读取,勿写入 CI、勿打入二进制。 |
cargo run -p secrets-mcp
- Web:
BASE_URL(登录、Dashboard、设置密码短语、创建 API Key)。 - MCP:Streamable HTTP 基址
{BASE_URL}/mcp,需Authorization: Bearer <api_key>+X-Encryption-Key: <hex>请求头。
加密架构(混合 E2EE)
密钥派生
用户在 Web Dashboard 设置密码短语,浏览器使用 **Web Crypto API(PBKDF2-SHA256,600k 次迭代)**在本地派生 256-bit AES 密钥。
- Salt(32B):首次设置时在浏览器生成,存入服务端
users.key_salt - key_check:派生密钥加密已知常量
"secrets-mcp-key-check",存入users.key_check,用于登录时验证密码短语 - 服务端不存储原始密钥,只存 salt + key_check
跨设备同步:新设备登录 → 输入相同密码短语 → 从服务端取 salt → 同样的 PBKDF2 → 得到相同密钥。
写入与读取流程
flowchart LR
subgraph Web["Web 浏览器(E2E)"]
P["密码短语"] --> K["PBKDF2 → 256-bit key"]
K --> Enc["AES-256-GCM 加密"]
K --> Dec["AES-256-GCM 解密"]
end
subgraph AI["AI 客户端(MCP)"]
HdrKey["X-Encryption-Key: hex"]
end
subgraph Server["secrets-mcp 服务端"]
Middleware["请求中临时持有 key\n请求结束即丢弃"]
DB[(PostgreSQL\nsecrets.encrypted = 密文\nentries.metadata = 明文)]
end
Enc -->|密文| Server
HdrKey -->|key + 请求| Middleware
Middleware <-->|加解密| DB
DB -->|密文| Dec
两种客户端对比
| Web 浏览器 | AI 客户端(MCP) | |
|---|---|---|
| 密钥位置 | 仅在浏览器内存 / sessionStorage | MCP 配置 headers 中 |
| 加解密位置 | 客户端(真正 E2E) | 服务端临时(请求级生命周期) |
| 安全边界 | 服务端零知识 | 依赖 TLS + 服务端内存隔离 |
敏感数据传输
- OAuth
client_secret只存服务端环境变量,不发给浏览器 - API Key 创建时原始 key 仅展示一次,库中只存 SHA-256 哈希
- X-Encryption-Key 随 MCP 请求经 TLS 传输,服务端仅在请求处理期间持有(不持久化)
- 生产环境必须走 HTTPS/TLS
AI 客户端配置
在 Web Dashboard 设置密码短语后,解锁页面会按客户端格式生成配置。常见客户端示例如下:
Cursor / Claude Desktop 风格:
{
"mcpServers": {
"secrets": {
"url": "https://secrets.example.com/mcp",
"headers": {
"Authorization": "Bearer sk_abc123...",
"X-Encryption-Key": "a1b2c3...(64位hex)"
}
}
}
}
OpenCode 风格:
{
"mcp": {
"secrets": {
"type": "remote",
"enabled": true,
"url": "https://secrets.example.com/mcp",
"headers": {
"Authorization": "Bearer sk_abc123...",
"X-Encryption-Key": "a1b2c3...(64位hex)"
}
}
}
}
数据模型
主表 entries(namespace、kind、name、tags、metadata,多租户时带 user_id)+ 子表 secrets(每行一个加密字段:field_name、encrypted)。另有 entries_history、secrets_history、audit_log,以及 users(含 key_salt、key_check、key_params)、oauth_accounts、api_keys。首次连库自动迁移建表。
| 位置 | 字段 | 说明 |
|---|---|---|
| entries | namespace | 一级隔离,如 refining、ricnsmart |
| entries | kind | server、service、key 等(可扩展) |
| entries | name | 人类可读标识 |
| entries | metadata | 明文 JSON(ip、url、key_ref 等) |
| secrets | field_name | 明文字段名,便于 schema 展示 |
| secrets | encrypted | AES-GCM 密文(含 nonce) |
| users | key_salt | PBKDF2 salt(32B),首次设置密码短语时写入 |
| users | key_check | 派生密钥加密已知常量,用于验证密码短语 |
| users | key_params | 派生算法参数,如 {"alg":"pbkdf2-sha256","iterations":600000} |
PEM 共享(key_ref)
同一 PEM 可被多条 server 记录引用:将 PEM 存为 kind=key 的 entry,在服务器条目的 metadata.key_ref 中写 key 的名称;轮换时只更新 key 对应记录即可。
审计日志
add、update、delete 等写操作写入 audit_log(操作类型、对象、摘要,不含 secret 明文)。
SELECT action, namespace, kind, name, actor, detail, created_at
FROM audit_log
ORDER BY created_at DESC
LIMIT 20;
项目结构
Cargo.toml
crates/secrets-core/ # db / crypto / models / audit / service
crates/secrets-mcp/ # MCP HTTP、Web、OAuth、API Key
scripts/
deploy/ # systemd、.env 示例
CI/CD(Gitea Actions)
见 .gitea/workflows/secrets.yml。
- 触发:任意分支
push,且变更路径包含crates/**、deploy/**、根目录Cargo.toml/Cargo.lock。 - 流水线:解析
crates/secrets-mcp/Cargo.toml版本 → 若secrets-mcp-<version>的 tag 已存在则复用现有 tag 继续构建,否则自动打 tag →cargo fmt/clippy --locked/test --locked→ 交叉编译x86_64-unknown-linux-musl的secrets-mcp。 - Release(可选):配置仓库 Secret
RELEASE_TOKEN(Gitea PAT,明文勿 base64)时,会通过 API 创建草稿 Release、在 Linux 构建成功后上传tar.gz与.sha256,再自动将草稿正式发布;未配置则跳过创建 Release 与产物上传,仅保留 tag 与构建结果。 - 部署(可选):仅在
main、feat/mcp或mcp分支且构建成功时,若已配置vars.DEPLOY_HOST、vars.DEPLOY_USER与secrets.DEPLOY_SSH_KEY,则deploy-mcp通过 SCP/SSH 更新目标机二进制并systemctl restart secrets-mcp。 - 通知(可选):
vars.WEBHOOK_URL为飞书 Webhook 时,构建/部署/发布节点会推送简要状态。
./scripts/setup-gitea-actions.sh # 通过 Gitea API 写入 RELEASE_TOKEN、WEBHOOK_URL、部署相关变量等
详见 AGENTS.md(发版规则、代码规范)。