- 删除 entries_history / audit_log / secrets_history 的 actor 列及写入逻辑 - MCP secrets_history 透传当前 user_id - Entry 增加 user_id,search 查询不再用伪 UUID - 迁移:保留 users.api_key,从 api_keys 表回退时生成新明文 key 并删表 - 文档:audit_log auth 语义、API Key 存储说明 Made-with: Cursor
7.2 KiB
7.2 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 已存在,默认允许复用现有 tag 继续构建;仅在需要新的发布版本时再 bump
version并cargo build同步Cargo.lock。 - 提交前优先运行
./scripts/release-check.sh(版本/tag +fmt+clippy --locked+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。
表结构(摘录)
entries (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID, -- 多租户:NULL=遗留行;非空=归属用户
namespace VARCHAR(64) NOT NULL,
kind VARCHAR(64) NOT NULL,
name VARCHAR(256) NOT NULL,
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()
)
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,
...
UNIQUE(provider, provider_id)
)
audit_log / history
与迁移脚本一致:audit_log、entries_history、secrets_history 用于审计与时间旅行恢复;字段定义见 crates/secrets-core/src/db.rs 内 migrate SQL。audit_log 中普通业务事件的 namespace/kind/name 对应 entry 坐标;登录类事件固定使用 namespace='auth',此时 kind/name 表示认证目标而非 entry 身份。
字段职责
| 字段 | 含义 | 示例 |
|---|---|---|
namespace |
隔离空间 | refining |
kind |
记录类型 | server, service, key |
name |
标识名 | gitea, i-example0… |
tags |
标签 | ["aliyun","prod"] |
metadata |
明文描述 | ip、url、key_ref |
secrets.field_name |
加密字段名(明文) | token, ssh_key |
secrets.encrypted |
密文 | AES-GCM |
PEM 共享(key_ref)
将共享 PEM 存为 kind=key 的 entry;其它记录在 metadata.key_ref 指向该 key 的 name。更新 key 记录后,引用方通过服务层解析合并逻辑即可使用新密钥(实现见 secrets_core::service)。
代码规范
- 错误:业务层
anyhow::Result,避免生产路径unwrap()。 - 异步:
tokio+sqlxasync。 - SQL:
sqlx::query/query_as参数绑定;动态 WHERE 仍须用占位符绑定。 - 日志:运维用
tracing;面向用户的 Web 响应走 axum handler。 - 审计:写操作成功后尽量
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/secrets.yml)。 - 版本与 tag:从
crates/secrets-mcp/Cargo.toml读版本;若远程已存在同名secrets-mcp-<version>tag,则复用现有 tag 继续构建;否则由 CI 创建并推送该 tag。 - 质量与构建:
fmt/clippy --locked/test --locked→x86_64-unknown-linux-musl发布构建secrets-mcp。 - Release(可选):
secrets.RELEASE_TOKEN(Gitea PAT)用于创建草稿 Release、上传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 |
监听地址,默认 0.0.0.0:9315。 |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
可选;仅运行时配置。 |
RUST_LOG |
如 secrets_mcp=debug。 |
SERVER_MASTER_KEY已不再需要。新架构下密钥由用户密码短语在客户端派生,服务端不持有。