- env_map:key_ref 解析传入 user_id;支持 folder/name;多条匹配时报错 - 文档同步 key_ref 说明 - bump secrets-mcp 0.3.1 → 0.3.2,更新 Cargo.lock Made-with: Cursor
8.9 KiB
Secrets MCP — AGENTS.md
本仓库为 MCP SaaS:secrets-core(业务与持久化)+ secrets-mcp(Streamable HTTP MCP、Web、OAuth、API Key)。对外入口见 crates/secrets-mcp。
提交 / 推送硬规则(优先于下文)
每次提交和推送前必须执行以下检查,无论是否明确「发版」:
- 涉及
crates/**、根目录Cargo.toml/Cargo.lock、secrets-mcp行为变更的提交,默认视为需要发版,除非明确说明「本次不发版」。 - 提交前检查
crates/secrets-mcp/Cargo.toml的version,再查 tag:git tag -l 'secrets-mcp-*'。若当前版本对应 tag 已存在且有代码变更,必须 bump 版本号并cargo build同步Cargo.lock。 - 提交前运行
./scripts/release-check.sh(版本/tag +fmt+clippy --locked+test --locked)。若脚本不存在或不可用,至少运行cargo fmt -- --check && cargo clippy --locked -- -D warnings && cargo test --locked。
项目结构
secrets/
Cargo.toml
crates/
secrets-core/ # db / crypto / models / audit / service
secrets-mcp/ # rmcp tools、axum、OAuth、Dashboard
scripts/
release-check.sh
setup-gitea-actions.sh
.gitea/workflows/secrets.yml
.vscode/tasks.json
数据库
- 建议库名:
secrets-mcp(专用实例,与历史库名区分)。 - 连接:环境变量
SECRETS_DATABASE_URL(本分支无本地配置文件路径)。 - 表:
entries(含user_id)、secrets、entries_history、secrets_history、audit_log、users、oauth_accounts,首次连接 auto-migrate(secrets-core的migrate)。 - Web 会话:与上项 同一数据库 URL;
secrets-mcp启动时对 tower-sessions 的 PostgreSQL 存储 auto-migrate(会话表与业务表共存于该实例,无需第二套连接串)。
表结构(摘录)
entries (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID, -- 多租户:NULL=遗留行;非空=归属用户
folder VARCHAR(128) NOT NULL DEFAULT '',
type VARCHAR(64) NOT NULL DEFAULT '',
name VARCHAR(256) NOT NULL,
notes TEXT NOT NULL DEFAULT '',
tags TEXT[] NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
version BIGINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
-- 唯一:UNIQUE(user_id, folder, name) WHERE user_id IS NOT NULL;
-- UNIQUE(folder, name) WHERE user_id IS NULL(单租户遗留)
secrets (
id UUID PRIMARY KEY DEFAULT uuidv7(),
entry_id UUID NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
field_name VARCHAR(256) NOT NULL,
encrypted BYTEA NOT NULL DEFAULT '\x',
version BIGINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(entry_id, field_name)
)
users / oauth_accounts
users (
id UUID PRIMARY KEY DEFAULT uuidv7(),
email VARCHAR(256),
name VARCHAR(256) NOT NULL DEFAULT '',
avatar_url TEXT,
key_salt BYTEA, -- PBKDF2 salt(32B),首次设置密码短语时写入
key_check BYTEA, -- 派生密钥加密已知常量,用于验证密码短语
key_params JSONB, -- 算法参数,如 {"alg":"pbkdf2-sha256","iterations":600000}
api_key TEXT UNIQUE, -- MCP Bearer token(当前实现为明文存储)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
oauth_accounts (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider VARCHAR(32) NOT NULL,
provider_id VARCHAR(256) NOT NULL,
email VARCHAR(256),
name VARCHAR(256),
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(provider, provider_id)
)
-- 另有唯一索引 UNIQUE(user_id, provider)(迁移中 idx_oauth_accounts_user_provider):同一用户每种 provider 至多一条关联。
audit_log / history
与迁移脚本一致:audit_log、entries_history、secrets_history 用于审计与时间旅行恢复;字段定义见 crates/secrets-core/src/db.rs 内 migrate SQL。audit_log 含可选 user_id(多租户下标识操作者;可空以兼容遗留数据)。audit_log 中普通业务事件使用 folder / type / name 对应 entry 坐标;登录类事件固定使用 folder='auth',此时 type/name 表示认证目标而非 entry 身份。
MCP 消歧(AI 调用)
按 name 定位条目的工具(get / update / 单条 delete / history / rollback):若该用户下仅一条匹配则直接执行;若多条(同 name、不同 folder)则返回错误并提示补全 folder。secrets_delete 的 dry_run=true 与真实删除使用相同消歧规则。
字段职责
| 字段 | 含义 | 示例 |
|---|---|---|
folder |
隔离空间(参与唯一键) | refining |
type |
软分类(不参与唯一键) | server, service, key, person |
name |
标识名 | gitea, aliyun |
notes |
非敏感说明 | 自由文本 |
tags |
标签 | ["aliyun","prod"] |
metadata |
明文描述 | ip、url、key_ref |
secrets.field_name |
加密字段名(明文) | token, ssh_key |
secrets.encrypted |
密文 | AES-GCM |
PEM 共享(key_ref)
将共享 PEM 存为 type=key 的 entry;其它记录在 metadata.key_ref 指向该 key 的 name(支持 folder/name 格式消歧)。更新 key 记录后,引用方通过服务层解析合并逻辑即可使用新密钥(实现见 secrets_core::service::env_map)。
代码规范
- 错误:业务层
anyhow::Result,避免生产路径unwrap()。 - 异步:
tokio+sqlxasync。 - SQL:
sqlx::query/query_as参数绑定;动态 WHERE 仍须用占位符绑定。 - 日志:运维用
tracing;面向用户的 Web 响应走 axum handler。tracing 字段风格:变量名即字段名时用简写(%var、?var、var),否则用显式形式(field = %expr)。 - 审计:写操作成功后尽量
audit::log_tx;失败可warn,不掩盖主错误。 - 加密:密钥由用户密码短语通过 PBKDF2-SHA256(600k 次) 在客户端派生,服务端只存
key_salt/key_check/key_params,不持有原始密钥。Web 客户端在浏览器本地完成加解密;MCP 客户端通过X-Encryption-Key请求头传递密钥,服务端临时解密后返回明文。 - MCP:tools 参数与 JSON Schema(
schemars)保持同步,鉴权以请求扩展中的用户上下文为准。
提交前检查
./scripts/release-check.sh
或手动:
cargo fmt -- --check
cargo clippy --locked -- -D warnings
cargo test --locked
发版前确认未重复 tag:
grep '^version' crates/secrets-mcp/Cargo.toml
git tag -l 'secrets-mcp-*'
CI/CD
- 触发:任意分支
push,且路径含crates/**、deploy/**、根目录Cargo.toml、Cargo.lock、.gitea/workflows/**(见.gitea/workflows/secrets.yml)。 - 版本与 tag:从
crates/secrets-mcp/Cargo.toml读版本;构建成功后打secrets-mcp-<version>:若远端已存在同名 tag,CI 会先删后于当前提交重建并推送(覆盖式发版)。 - 质量与构建:
fmt/clippy --locked/test --locked→x86_64-unknown-linux-musl发布构建secrets-mcp。 - Release(可选):
secrets.RELEASE_TOKEN(Gitea PAT)用于通过 API 创建或更新该 tag 的 Release(非 draft)、上传tar.gz+.sha256;未配置则跳过 API Release,仅 tag + 构建。 - 部署(可选):仅
main、feat/mcp、mcp分支在构建成功时跑deploy-mcp;需vars.DEPLOY_HOST、vars.DEPLOY_USER、secrets.DEPLOY_SSH_KEY。勿把 OAuth/DB 等写进 workflow,用deploy/.env.example在目标机配置。 - Secrets 写法:Actions secrets 须为原始值(PEM、PAT 明文),勿 base64;否则 SSH/Release 会失败。勿在 CI 中保存
GOOGLE_CLIENT_SECRET、DB 密码。 - 通知:
vars.WEBHOOK_URL(可选,飞书)。
环境变量(secrets-mcp)
| 变量 | 说明 |
|---|---|
SECRETS_DATABASE_URL |
必填。PostgreSQL URL。 |
BASE_URL |
对外基址;OAuth 回调 ${BASE_URL}/auth/google/callback。 |
SECRETS_MCP_BIND |
监听地址,默认 127.0.0.1:9315(容器/远程直接暴露时需改为 0.0.0.0:9315)。 |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
可选;仅运行时配置。 |
RUST_LOG |
如 secrets_mcp=debug。 |
SERVER_MASTER_KEY已不再需要。新架构下密钥由用户密码短语在客户端派生,服务端不持有。