mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
114 lines
7.6 KiB
Markdown
114 lines
7.6 KiB
Markdown
# ADR-0035:Approved Action 人工恢复授权与强认证事实
|
||
|
||
- 状态:Proposed
|
||
- 日期:2026-07-19
|
||
- 关联:QL-RFC-0001、ADR-0028、ADR-0029、ADR-0030、ADR-0031、ADR-0033
|
||
|
||
## 上下文
|
||
|
||
ADR-0033 只允许人工把 post-start unknown action 显式终结为 succeeded、failed 或 blocked,禁止把旧 dispatch 重置后再次执行。这个操作会改变审计事实,也可能掩盖一个已经发生但无法验证的外部副作用,因此普通登录、可变 username、Agent 身份或仅在应用层判断一次角色都不足以授权。
|
||
|
||
人工服务先做 Policy 判断、随后 Repository 写终态时还存在撤权竞态:如果 owner/admin binding 或 Project version 在两步之间变化,旧的 allow 结果不能继续生效。授权记录若在终态提交之后另写,又会留下“动作已经被人裁决,但无法证明当时认证与授权上下文”的崩溃窗口。
|
||
|
||
因此人工恢复必须把稳定 User、近期强认证、Project Policy version fence 和低敏授权事实绑定到同一个终态事务。
|
||
|
||
## 决策
|
||
|
||
### 1. `approval.recover` 是独立的 Project permission
|
||
|
||
人工恢复只请求 `approval.recover`,不复用 `approval.decide`、`project.manage` 或通配 Tool 权限。固定角色矩阵为:
|
||
|
||
- owner、admin:允许;
|
||
- operator、viewer:拒绝;
|
||
- 未绑定、已撤销或 archived Project:默认拒绝。
|
||
|
||
首版继续使用静态 Policy Core;未来接 OPA 或自定义策略时也必须返回 Project/RoleBinding version fence,不能只返回一个布尔 allow。
|
||
|
||
### 2. 只接受仍有效的稳定 User principal
|
||
|
||
应用服务接收 ADR-0029 的 exact-shape `AuthenticatedPrincipal`,并在读取 recovery storage 前验证:
|
||
|
||
- `subject.type=user`;
|
||
- principal 在服务时钟上满足 `authenticatedAtMs <= now < expiresAtMs`;
|
||
- assurance 为 `multi_factor`、`hardware` 或 `local_console`;
|
||
- `now - authenticatedAtMs <= 5 分钟`。
|
||
|
||
`single_factor`、`service`、Agent、API App、MCP Client、Worker 和 System 均不能进行人工恢复。legacy `twoFactorActivated` 仍不是 MFA 事实;只有未来可信 authentication adapter 验证真实 ceremony 后才能产生上述 assurance。本 ADR 不定义 MFA 协议、恢复码或 local-console issuer。
|
||
|
||
### 3. Policy 判断必须携带可提交的 version fence
|
||
|
||
服务从 recovery snapshot 取得 immutable Project ID,以稳定 User 请求 `approval.recover`。只有 `allow` 且 Project version、当前 RoleBinding version 都存在时才构造授权事实。
|
||
|
||
SQLite Repository 在同一个 `BEGIN IMMEDIATE` 中重新读取最新 Project 与该 User 的 RoleBinding version:任一 version 不一致即以 fence rejection 失败,且在写 recovery resolution 或 execution 终态前退出。这样 revoke、role change、Project archive 或其他 version mutation 无法穿透已经计算出的 allow。
|
||
|
||
### 4. `0024` 授权事实与人工终态原子提交
|
||
|
||
`ApprovedActionRecoveryAuthorizationFacts` 与 human resolution 一对一,保存:
|
||
|
||
- dispatch、Project、manual mutation ID 和稳定 User ID;
|
||
- authentication ID、强 assurance、认证时间;
|
||
- Policy 判断使用的 Project/RoleBinding version;
|
||
- 授权时间;
|
||
- 对以上 canonical 字段计算的 SHA-256 fact digest。
|
||
|
||
表只允许 User 和三种强 assurance,要求正 version、认证时间不晚于授权时间且间隔不超过五分钟,并通过外键绑定 recovery resolution 与 Project。它不保存 cookie、JWT、MFA secret、recovery code、请求 header 或无界理由。
|
||
|
||
Repository 在一个事务内依次复验 execution/recovery fence、复验 Policy version、插入 human resolution、推进 execution/control 终态并插入授权事实。任一约束、trigger 或 commit 失败全部回滚,不能留下“有终态无授权事实”或“有授权事实无终态”。automatic evidence resolution 不创建人工授权事实。
|
||
|
||
### 5. 幂等重放必须包含相同授权事实
|
||
|
||
相同 manual mutation 只有在 decision、reason、evidence、User、时间和 authorization fact 全部精确一致时返回原结果。字段漂移、digest 不匹配或一条旧 human resolution 缺少 `0024` fact 时不补写、不重新解释,稳定返回 terminal/conflict 语义。
|
||
|
||
服务在重放前仍验证当前 principal 和当前 Policy;历史 allow 事实只用于审计与精确幂等,不成为永久授权 capability。
|
||
|
||
### 6. 当前保持 production unreachable
|
||
|
||
本切片已经实现 permission matrix、强认证应用服务、`0024` migration、SQLite 原子 Repository 和 contract tests,但没有注册到 app、loader、legacy service、API、CLI、MCP 或 UI。生产启用仍需要:
|
||
|
||
- 真实 MFA/hardware/local-console authentication adapter 与 credential 生命周期;
|
||
- 人工恢复列表、低敏证据预览、二次确认、rate limit 和审计 API/UI;
|
||
- 对外错误屏蔽,避免未授权调用者区分 dispatch 是否存在;
|
||
- profile-aware recovery lifecycle、指标、告警与积压门禁;
|
||
- PostgreSQL adapter 与多副本 revoke/resolve 竞争 contract;
|
||
- edge 设备和 cluster 节点上的故障注入与资源门禁。
|
||
|
||
## 影响
|
||
|
||
正面影响:
|
||
|
||
- 高风险 unknown 终结不能由 Agent、普通 session 或 operator 越权执行;
|
||
- 授权撤销竞态由数据库 version fence 关闭;
|
||
- 认证、授权与终态形成一个可验证、低敏、原子的审计事实;
|
||
- edge 只增加一次人工操作时的短事务和单行事实,不增加 watcher、timer 或常驻连接;
|
||
- cluster adapter 可以复用相同语义,通过 PostgreSQL row lock 实现多副本一致裁决。
|
||
|
||
代价与风险:
|
||
|
||
- 单用户部署若没有可信强认证入口,将不能使用人工恢复;这是安全门禁,不应降级为密码确认;
|
||
- 每条人工 resolution 增加一行保留事实和关联索引;
|
||
- authentication ID 虽非 Secret,仍属于安全审计标识,API 默认不得向普通读取者暴露;
|
||
- 当前只有 SQLite adapter,不能据此宣称 cluster-control 已可用。
|
||
|
||
## 未选择的方案
|
||
|
||
1. **owner/admin 登录即可恢复**:single-factor session 不足以授权未知副作用终结,拒绝。
|
||
2. **复用 `approval.decide`**:审批新动作与裁决已发生但未知的动作风险不同,拒绝。
|
||
3. **只在应用层检查 Policy**:检查与提交之间存在 revoke/archive 竞态,拒绝。
|
||
4. **终态提交后异步写审计日志**:崩溃会产生不可证明的人工裁决,拒绝。
|
||
5. **把完整 JWT/MFA 证明写数据库**:扩大 Secret 与隐私暴露面,拒绝。
|
||
6. **旧单用户环境自动提升为 local console**:UID、TTY 或私有文件本身不能证明可信 ceremony,拒绝。
|
||
7. **授权事实缺失时自动补写**:无法还原历史认证与 Policy snapshot,拒绝。
|
||
|
||
## 验证要求
|
||
|
||
- owner/admin 允许,operator/viewer/未绑定/revoked/archived 拒绝;
|
||
- 非 User、过期 principal、single-factor/service 或超过五分钟的强认证在 storage read 前拒绝;
|
||
- Project/RoleBinding 在 Policy allow 后变化时,Repository 在任何终态写入前 fence rejection;
|
||
- resolution、execution/control 终态与 authorization fact 同事务提交,fact 插入失败全部回滚;
|
||
- fact exact-shape、强 assurance、时间窗口、version、digest 或 identity 任一漂移均 fail closed;
|
||
- 精确 manual mutation 重放返回同一结果,变形重放不得覆盖原事实;
|
||
- automatic evidence resolution 不依赖或伪造人工授权事实;
|
||
- migration/schema ownership 覆盖表、索引、约束和外键;
|
||
- production reachability 搜索证明服务和 Repository 未被旧 app/loader/API/service 路径导入;
|
||
- Node 22 兼容门禁与固定 Node 24 目标门禁均通过。
|