# secrets-mcp Workspace:**`secrets-core`** + **`secrets-mcp`**(HTTP Streamable MCP + Web)。多租户密钥与元数据存 PostgreSQL;用户通过 **Google OAuth** 登录,**API Key** 鉴权 MCP 请求;秘密数据用**用户密码短语派生的密钥**在客户端加密,服务端不持有原始密钥。 ## 安装 ```bash cargo build --release -p secrets-mcp # 产物: target/release/secrets-mcp ``` 发版产物见 Gitea Release(tag:`secrets-mcp-`,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` | 监听地址,默认 `127.0.0.1:9315`。容器内或直接对外暴露端口时请改为 `0.0.0.0:9315`;反代时常为 `127.0.0.1:9315`。 | | `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | 可选;不配置则无 Google 登录入口。运行时从环境读取,勿写入 CI、勿打入二进制。 | ```bash cargo run -p secrets-mcp ``` - **Web**:`BASE_URL`(登录、Dashboard、设置密码短语、创建 API Key)。 - **MCP**:Streamable HTTP 基址 `{BASE_URL}/mcp`,需 `Authorization: Bearer ` + `X-Encryption-Key: ` 请求头。 ## 加密架构(混合 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 → 得到相同密钥。 ### 写入与读取流程 ```mermaid 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** 当前存放在 `users.api_key`,Dashboard 会明文展示并可重置 - **X-Encryption-Key** 随 MCP 请求经 TLS 传输,服务端仅在请求处理期间持有(不持久化) - **生产环境必须走 HTTPS/TLS** ## AI 客户端配置 在 Web Dashboard 设置密码短语后,解锁页面会按客户端格式生成配置。常见客户端示例如下: `Cursor / Claude Desktop` 风格: ```json { "mcpServers": { "secrets": { "url": "https://secrets.example.com/mcp", "headers": { "Authorization": "Bearer sk_abc123...", "X-Encryption-Key": "a1b2c3...(64位hex)" } } } } ``` `OpenCode` 风格: ```json { "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`、`api_key`)、**`oauth_accounts`**。首次连库自动迁移建表。 | 位置 | 字段 | 说明 | |------|------|------| | 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 明文)。 其中业务条目事件使用 `[namespace/kind] name` 语义;登录类事件使用 `namespace='auth'`,此时 `kind/name` 表示认证目标(例如 `oauth/google`),不表示某条 secrets entry。 ```sql SELECT action, namespace, kind, name, 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`](.gitea/workflows/secrets.yml)。 - **触发**:任意分支 `push`,且变更路径包含 `crates/**`、`deploy/**`、根目录 `Cargo.toml` / `Cargo.lock`。 - **流水线**:解析 `crates/secrets-mcp/Cargo.toml` 版本 → 若 `secrets-mcp-` 的 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 时,构建/部署/发布节点会推送简要状态。 ```bash ./scripts/setup-gitea-actions.sh # 通过 Gitea API 写入 RELEASE_TOKEN、WEBHOOK_URL、部署相关变量等 ``` 详见 [AGENTS.md](AGENTS.md)(发版规则、代码规范)。