refactor: entries + secrets 双表,search 展示 field schema,key_ref PEM 共享
Some checks failed
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 1m57s
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 3s
Secrets CLI - Build & Release / Build (macOS aarch64 + x86_64) (push) Successful in 51s
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Successful in 1m6s
Secrets CLI - Build & Release / 发布草稿 Release (push) Has been cancelled
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
Some checks failed
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 1m57s
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 3s
Secrets CLI - Build & Release / Build (macOS aarch64 + x86_64) (push) Successful in 51s
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Successful in 1m6s
Secrets CLI - Build & Release / 发布草稿 Release (push) Has been cancelled
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
- secrets 表拆为 entries(主表)+ secrets(每字段一行) - search 无需 master_key 即可展示 secrets 字段名、类型、长度 - inject/run 支持 metadata.key_ref 引用 kind=key 记录,PEM 轮换 O(1) - entries_history + secrets_history 字段级历史,rollback 按 version 恢复 - 移除迁移用 DROP 语句,migrate 幂等 - v0.8.0 Made-with: Cursor
This commit is contained in:
124
AGENTS.md
124
AGENTS.md
@@ -7,7 +7,7 @@
|
||||
3. 若当前版本对应 tag 已存在,必须先 bump `Cargo.toml` 的 `version`,再执行 `cargo build` 同步 `Cargo.lock`,然后才能提交。
|
||||
4. 提交前优先运行 `./scripts/release-check.sh`;该脚本会检查重复版本并执行 `cargo fmt -- --check && cargo clippy --locked -- -D warnings && cargo test --locked`。
|
||||
|
||||
跨设备密钥与配置管理 CLI 工具,将 refining / ricnsmart 两个项目的服务器信息、服务凭据存储到 PostgreSQL 18,供 AI 工具读取上下文。敏感数据(encrypted 字段)使用 AES-256-GCM 加密,主密钥由 Argon2id 从主密码派生并存入平台安全存储(macOS Keychain / Windows Credential Manager / Linux keyutils)。
|
||||
跨设备密钥与配置管理 CLI 工具,将服务器信息、服务凭据等存储到 PostgreSQL 18,供 AI 工具读取上下文。每个加密字段单独行存储(`secrets` 子表),字段名、类型、长度以明文保存,主密钥由 Argon2id 从主密码派生并存入平台安全存储(macOS Keychain / Windows Credential Manager / Linux keyutils)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
@@ -17,19 +17,19 @@ secrets/
|
||||
main.rs # CLI 入口,clap 命令定义,auto-migrate,--verbose 全局参数
|
||||
output.rs # OutputMode 枚举 + TTY 检测(TTY→text,非 TTY→json-compact)
|
||||
config.rs # 配置读写:~/.config/secrets/config.toml(database_url)
|
||||
db.rs # PgPool 创建 + 建表/索引(幂等,含 audit_log + kv_config + secrets_history)
|
||||
db.rs # PgPool 创建 + 建表/索引(DROP+CREATE,含所有表)
|
||||
crypto.rs # AES-256-GCM 加解密、Argon2id 派生、OS 钥匙串
|
||||
models.rs # Secret 结构体(sqlx::FromRow + serde,含 version 字段)
|
||||
audit.rs # 审计写入:log_tx(事务内)/ log(池,保留备用)
|
||||
models.rs # Entry + SecretField 结构体(sqlx::FromRow + serde)
|
||||
audit.rs # 审计写入:log_tx(事务内)
|
||||
commands/
|
||||
init.rs # init 命令:主密钥初始化(每台设备一次)
|
||||
add.rs # add 命令:upsert,事务化,含历史快照,支持 key:=json 类型化值与嵌套路径写入
|
||||
add.rs # add 命令:upsert entries + 逐字段写入 secrets,含历史快照
|
||||
config.rs # config 命令:set-db / show / path(持久化 database_url)
|
||||
search.rs # search 命令:多条件查询,公开 fetch_rows / build_env_map
|
||||
delete.rs # delete 命令:事务化,含历史快照
|
||||
update.rs # update 命令:增量更新,CAS 并发保护,含历史快照
|
||||
rollback.rs # rollback / history 命令:版本回滚与历史查看
|
||||
run.rs # inject / run 命令:临时环境变量注入
|
||||
search.rs # search 命令:多条件查询,展示 secrets 字段 schema(无需 master_key)
|
||||
delete.rs # delete 命令:事务化,CASCADE 删除 secrets,含历史快照
|
||||
update.rs # update 命令:增量更新,secrets 行级 UPSERT/DELETE,CAS 并发保护
|
||||
rollback.rs # rollback / history 命令:按 entry_version 恢复 entry + secrets
|
||||
run.rs # inject / run 命令:逐字段解密 + key_ref 引用解析
|
||||
upgrade.rs # upgrade 命令:检查、校验摘要并下载最新版本,自动替换二进制
|
||||
scripts/
|
||||
release-check.sh # 发版前检查版本号/tag 是否重复,并执行 fmt/clippy/test
|
||||
@@ -44,19 +44,18 @@ secrets/
|
||||
- **Host**: `<host>:<port>`
|
||||
- **Database**: `secrets`
|
||||
- **连接串**: `postgres://postgres:<password>@<host>:<port>/secrets`
|
||||
- **表**: `secrets`(主表)+ `audit_log`(审计表)+ `kv_config`(Argon2 salt 等),首次连接自动建表(auto-migrate)
|
||||
- **表**: `entries`(主表)+ `secrets`(加密字段子表)+ `entries_history` + `secrets_history` + `audit_log` + `kv_config`,首次连接自动建表(auto-migrate)
|
||||
|
||||
### 表结构
|
||||
|
||||
```sql
|
||||
secrets (
|
||||
entries (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(), -- PG18 时间有序 UUID
|
||||
namespace VARCHAR(64) NOT NULL, -- 一级隔离: "refining" | "ricnsmart"
|
||||
kind VARCHAR(64) NOT NULL, -- 类型: "server" | "service"(可扩展)
|
||||
kind VARCHAR(64) NOT NULL, -- 类型: "server" | "service" | "key"(可扩展)
|
||||
name VARCHAR(256) NOT NULL, -- 人类可读标识
|
||||
tags TEXT[] NOT NULL DEFAULT '{}', -- 灵活标签: ["aliyun","hongkong"]
|
||||
metadata JSONB NOT NULL DEFAULT '{}', -- 明文描述: ip, desc, domains, location...
|
||||
encrypted BYTEA NOT NULL DEFAULT '\x', -- AES-256-GCM 密文: nonce(12B)||ciphertext+tag
|
||||
version BIGINT NOT NULL DEFAULT 1, -- 乐观锁版本号,每次写操作自增
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
@@ -65,26 +64,24 @@ secrets (
|
||||
```
|
||||
|
||||
```sql
|
||||
secrets_history (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
secret_id UUID NOT NULL, -- 对应 secrets.id
|
||||
namespace VARCHAR(64) NOT NULL,
|
||||
kind VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
version BIGINT NOT NULL, -- 被快照时的版本号
|
||||
action VARCHAR(16) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback'
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
encrypted BYTEA NOT NULL DEFAULT '\x', -- 快照时的加密密文
|
||||
actor VARCHAR(128) NOT NULL DEFAULT '',
|
||||
created_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, -- 明文字段名: "username", "token", "ssh_key"
|
||||
field_type VARCHAR(32) NOT NULL DEFAULT 'string', -- 明文类型: "string"|"number"|"boolean"|"json"
|
||||
value_len INT NOT NULL DEFAULT 0, -- 明文原始值字符数(PEM≈4096,token≈40)
|
||||
encrypted BYTEA NOT NULL DEFAULT '\x', -- 仅加密值本身:nonce(12B)||ciphertext+tag
|
||||
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)
|
||||
)
|
||||
```
|
||||
|
||||
```sql
|
||||
kv_config (
|
||||
key TEXT PRIMARY KEY, -- 如 'argon2_salt'
|
||||
value BYTEA NOT NULL -- Argon2id salt,首台设备 init 时生成
|
||||
value BYTEA NOT NULL -- Argon2id salt,首台设备 init 时生成
|
||||
)
|
||||
```
|
||||
|
||||
@@ -93,26 +90,85 @@ kv_config (
|
||||
```sql
|
||||
audit_log (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
action VARCHAR(32) NOT NULL, -- 'add' | 'update' | 'delete'
|
||||
action VARCHAR(32) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback'
|
||||
namespace VARCHAR(64) NOT NULL,
|
||||
kind VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
detail JSONB NOT NULL DEFAULT '{}', -- 变更摘要(tags/meta keys/secret keys,不含 value)
|
||||
actor VARCHAR(128) NOT NULL DEFAULT '', -- 操作者($USER 环境变量)
|
||||
actor VARCHAR(128) NOT NULL DEFAULT '', -- 操作者($USER 环境变量)
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
```
|
||||
|
||||
### entries_history 表结构
|
||||
|
||||
```sql
|
||||
entries_history (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
entry_id UUID NOT NULL,
|
||||
namespace VARCHAR(64) NOT NULL,
|
||||
kind VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
version BIGINT NOT NULL, -- 被快照时的版本号
|
||||
action VARCHAR(16) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback'
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
actor VARCHAR(128) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
```
|
||||
|
||||
### secrets_history 表结构
|
||||
|
||||
```sql
|
||||
secrets_history (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
entry_id UUID NOT NULL,
|
||||
secret_id UUID NOT NULL, -- 对应 secrets.id
|
||||
entry_version BIGINT NOT NULL, -- 关联 entries_history 的版本号
|
||||
field_name VARCHAR(256) NOT NULL,
|
||||
field_type VARCHAR(32) NOT NULL DEFAULT 'string',
|
||||
value_len INT NOT NULL DEFAULT 0,
|
||||
encrypted BYTEA NOT NULL DEFAULT '\x',
|
||||
action VARCHAR(16) NOT NULL, -- 'add' | 'update' | 'delete' | 'rollback'
|
||||
actor VARCHAR(128) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
```
|
||||
|
||||
### 字段职责划分
|
||||
|
||||
| 字段 | 存什么 | 示例 |
|
||||
|------|--------|------|
|
||||
| `namespace` | 项目/团队隔离 | `refining`, `ricnsmart` |
|
||||
| `kind` | 记录类型 | `server`, `service` |
|
||||
| `kind` | 记录类型 | `server`, `service`, `key` |
|
||||
| `name` | 唯一标识名 | `i-uf63f2uookgs5uxmrdyc`, `gitea` |
|
||||
| `tags` | 多维分类标签 | `["aliyun","hongkong","ricn"]` |
|
||||
| `metadata` | 明文非敏感信息 | `{"ip":"47.243.154.187","desc":"Grafana","domains":["..."]}` |
|
||||
| `encrypted` | 敏感凭据,AES-256-GCM 加密存储 | 二进制密文,解密后为 `{"ssh_key":"...","password":"..."}` |
|
||||
| `metadata` | 明文非敏感信息 | `{"ip":"47.243.154.187","desc":"Grafana","key_ref":"ricn-hk-260127"}` |
|
||||
| `secrets.field_name` | 加密字段名(明文) | `"username"`, `"token"`, `"ssh_key"` |
|
||||
| `secrets.field_type` | 值类型(明文) | `"string"`, `"number"`, `"boolean"`, `"json"` |
|
||||
| `secrets.value_len` | 原始值字符数(明文) | `4`(root),`40`(token),`4096`(PEM) |
|
||||
| `secrets.encrypted` | 仅加密值本身 | AES-256-GCM 密文 |
|
||||
|
||||
### PEM 共享机制(key_ref)
|
||||
|
||||
同一 PEM 被多台服务器共享时,将 PEM 存为独立的 `kind=key` 记录,服务器通过 `metadata.key_ref` 引用:
|
||||
|
||||
```bash
|
||||
# 1. 存共享 PEM
|
||||
secrets add -n refining --kind key --name ricn-hk-260127 \
|
||||
--tag aliyun --tag hongkong \
|
||||
-s content=@./keys/ricn-hk-260127.pem
|
||||
|
||||
# 2. 服务器通过 metadata.key_ref 引用(inject/run 时自动合并 key 的 secrets)
|
||||
secrets add -n refining --kind server --name i-j6c39dmtkr26vztii0ox \
|
||||
-m ip=47.243.154.187 -m key_ref=ricn-hk-260127 \
|
||||
-s username=ecs-user
|
||||
|
||||
# 3. 轮换只需更新 key 记录,所有引用服务器自动生效
|
||||
secrets update -n refining --kind key --name ricn-hk-260127 \
|
||||
-s content=@./keys/new-key.pem
|
||||
```
|
||||
|
||||
## 数据库配置
|
||||
|
||||
@@ -175,7 +231,7 @@ secrets init
|
||||
# --name gitea | i-uf63f2uookgs5uxmrdyc | mqtt
|
||||
# --tag aliyun | hongkong | production
|
||||
# -q / --query mqtt | grafana | gitea (模糊匹配 name/namespace/kind/tags/metadata)
|
||||
# --show-secrets 已弃用;search 不再直接展示 secrets
|
||||
# secrets schema search 默认展示 secrets 字段名、类型与长度(无需 master_key)
|
||||
# -f / --field metadata.ip | metadata.url | metadata.default_org
|
||||
# --summary 不带值的 flag,仅返回摘要(name/tags/desc/updated_at)
|
||||
# --limit 20 | 50(默认 50)
|
||||
|
||||
Reference in New Issue
Block a user