Files
qinglong/docs/adr/ADR-0207-authenticated-local-secret-product-cli.md

182 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0207:强认证 Local Secret 产品 CLI 与事务内 Credential Fence
- 状态:Accepted
- 日期:2026-07-29
- 关联 RFCQL-RFC-0001 D-05、D-27、D-37、D-65、D-72、D-73、D-175、
D-197
- 关联 ADRADR-0074、ADR-0076、ADR-0085、ADR-0086、ADR-0185、ADR-0193
- 部分取代:ADR-0074 第 6 节“本机 CLI 保持不可达”
## 背景
ADR-0074 已有本机 Secret 加密、Policy、Project/RoleBinding fence 和 envelope +
allowed audit 原子提交,但 `@qinglong/local-secret-admin` 一直没有生产消费者。
部署用户即使完成 Fresh Setup 和首 Owner ceremony,也只能得到 Secret keyring,不能
用受支持的产品入口创建或轮换 Secret。
这同时暴露两个容易被误判的架构问题:
1. `local-secret-admin` 只有一个源码文件、没有生产 importer,看起来像过细的
workspace package
2. 若为了减少 package 数把它并入 `local-application``local-admin`
`local-owner-console` 根入口,常驻 Edge 产物或 DDL/Owner ceremony authority
又会携带 plaintext Secret 写能力。
ADR-0185 已确定 package 是交付和 authority 边界,不是按文件数划分的源码目录。
因此本轮不能通过合并包来“解决”无消费者问题,而应让该 authority 获得一个精确、
短生命周期、不可被常驻进程导入的产品消费者。
## 决策
### 1. 保留 22 个 package,不新增或合并 workspace importer
`@qinglong/local-secret-admin` 继续是独立高权限 package
- `local-application`、Edge/Standalone application artifact 和 Cluster/Worker
仍禁止导入;
- 只允许 `local-owner-cli/src/secretCommand.ts` 导入 package 根入口;
- 新入口使用现有 `@qinglong/local-owner-cli``ql3-secret` binary 和
`./secret-command` subpath,不创建第 23 个 package
- `@qinglong/local-command-file` 仍是三个上层消费者共享的稳定叶子;
`@qinglong/local-identity` 仍隔离 credential verifier 与 POSIX/SQLite
composition。单文件不是合并条件。
依赖审计必须同时拒绝 Owner CLI 的其他文件、未知 subpath 和任意常驻 package
导入 Secret admin authority。
### 2. 使用专用 SQLite Secret Administration composition
`@qinglong/local-sqlite/secret-administration` 只暴露本次命令所需能力:
- readiness
- API credential 与 Owner pepper provenance 查询;
- Project Policy
- Local Secret authorized mutation
- Security Audit
- credential fence 激活;
- 有界关闭。
它不暴露 Task/Run/Scheduler、Plugin Package、DDL migration、GC、recovery 或
destructive authority。每次 `ql3-secret` 只打开一个连接,执行一个 command,
随后关闭;不增加 daemon、timer、watcher、listener、端口或常驻内存。
### 3. Credential fence 必须在 Secret 写事务内重新验证
仅在 CLI 开始时验证 credential 不够:另一个管理进程可能在 Policy 判断与
`BEGIN IMMEDIATE` 之间 revoke credential。
`LocalSqliteRunRepository` 新增默认缺失的内部
`beforeAuthorizedLocalSecretMutation` barrier。普通 runtime、测试和既有
composition 不传入该 barrier,行为不变;专用 Secret composition 激活 exact
credential fence 后,barrier 在 Secret transaction 已取得写锁后重新验证:
- credential ID/version/state
- User subject/status
- secret digest 与有效期;
- pepper key ID/state/material digest。
缺失、变化或验证异常统一转换为
`LocalSecretAuthorizationFenceConflictError`,整个 envelope 和 allowed audit
事务回滚。Project/RoleBinding fence 仍由原 repository 在同一事务继续验证。
### 4. Plaintext 不进入 command file、stdout、stderr 或 audit
`ql3-secret` 只接受:
```text
ql3-secret run --command-file /absolute/private-command.json
```
command file 与 Secret value file 都必须是当前 POSIX UID 持有的 `0600` regular
file,路径规范、无 symlink,并位于 deployment root 下。command file 只记录
`secretValueFilePath`value file 使用 bounded exact-shape JSON
```json
{
"schemaVersion": 1,
"kind": "qinglong3-local-secret-value",
"value": "plaintext"
}
```
value 最大 16 KiB。读取使用既有 private-file inode/owner/mode/size 双检查并清零
原始 BufferJavaScript string 生命周期限制在单次命令进程。成功只返回 opaque
SecretRef、version 和 `inserted|existing`。错误及 security audit 只包含固定 code/
reason,不记录 plaintext、credential token、key material、文件路径或
authentication proof。
### 5. 创建与轮换使用同一 CAS 命令
唯一 mutation 是 `secret.put`
- `expectedCurrentVersion = 0` 表示创建;
- 正整数表示从精确版本轮换;
- mutation UUID、request ID 和独立 failure-audit UUID 都由 operator 固定;
- response-loss 使用原 command/value file 原样重跑;
- same mutation + same plaintext 返回 `existing`
- same mutation + different plaintext、版本漂移或 policy/credential 漂移均失败关闭。
CLI 不提供 plaintext read、decrypt、list-all、delete、key rotation、rekey 或远程
HTTP 入口。
## 对低配路由器与集群节点的影响
- Edge/Standalone application package closure 不新增
`local-secret-admin`,常驻 bytes/files/modules/RSS 不因本命令增加;
- 管理 CLI 仅在人工调用时加载,完成后释放 SQLite、keyring material 和进程内存;
- 没有目录扫描、后台重试、定时审计、socket 或额外数据库连接常驻;
- Cluster 节点仍使用独立 PostgreSQL/KMS/Vault 管理设计,本机 CLI 不获得 Cluster
credential、RBAC 或远程管理能力。
## 不采用方案
### 合并 `local-secret-admin` 到 application 或 local-admin
会让常驻 runtime 或 DDL authority 携带 plaintext Secret 写能力,破坏
ADR-0074/0185 的交付隔离。
### 合并到 `local-owner-console` 根入口
Owner bootstrap、credential recovery 与 Secret mutation 的 authority 生命周期
不同;合并会使所有 Owner ceremony 默认解析 Secret crypto/storage 依赖。
### 把 plaintext 直接放入 command JSON 或命令行参数
command 需要长期保留以支持 response-loss replayargv 又可能被进程列表读取。
单独私有 value file 能让命令事实与敏感值使用不同保留策略。
### 只在 transaction 之前调用 `authenticated.confirm()`
无法关闭跨进程 credential revoke 竞态。最终 fence 必须在 SQLite write lock 内
重验。
### 增加本机 Secret HTTP API
会新增远程攻击面、监听端口、rate-limit/session/CSRF/TLS 责任,不属于本次
Local Owner console 切片。
## 验收证据
- `ql3-secret` 真实 SQLite/CLI 专项 5/5
- create、exact replay、rotate
- ciphertext/audit/stdout/stderr 均不含 plaintext/token
-`0600` value file 在 mutation 前失败并写低敏审计;
- viewer 被 Policy 拒绝且 envelope 为零;
- revoked credential 在读取 Secret 前失败;
- credential 在预检后被 revoke 时,transaction barrier 回滚 envelope。
-`local-secret-admin` 6/6 全绿;
- dependency audit 负向契约 33/33,证明只有 exact Owner CLI 文件能导入
Secret admin 与专用 SQLite subpath
- TypeScript 对 `local-sqlite` 变更闭包和完整 `local-owner-cli` 均通过;
- workspace importer 仍为 22,没有新增第三方或生产依赖;
- Edge application 仍不依赖 `local-owner-cli``local-secret-admin`。完整 artifact
数字须在依赖目录恢复后重新采集,不能用源码图代替制品证据。
## 后续
- Project/RoleBinding 管理 CLI、Secret approval/break-glass
- Secret key rotation、历史 envelope rekey、retirement proof 和备份恢复;
- audit query/retention/export/alert
- PostgreSQL/KMS/Vault authority 与 Remote Worker 临时 delivery
- 固定 Edge 多架构上的管理命令 RSS、真实闪存写入和断电恢复证据。