mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 03:18:09 +08:00
147 lines
6.5 KiB
Markdown
147 lines
6.5 KiB
Markdown
# ADR-0214:Owner 围栏化、有界的 Local RoleBinding 查询
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-07-29
|
||
- 关联 RFC:QL-RFC-0001 D-05、D-27、D-37、D-65、D-72、D-73、D-175、
|
||
D-198、D-200、D-203、D-204
|
||
- 关联 ADR:ADR-0028、ADR-0185、ADR-0208、ADR-0210、ADR-0213
|
||
|
||
## 背景
|
||
|
||
ADR-0208 的 `policy.role-binding.put|revoke` 要求 target 的 current version,但产品
|
||
入口不能读取当前 RoleBinding。operator 仍需直接查询 SQLite,既绕过认证和审计,也
|
||
容易把 append-only 历史行误当成当前授权。
|
||
|
||
RoleBinding 查询与 ADR-0213 的 Project 拓扑查询不是同一种 authority:
|
||
|
||
- Project create/archive/restore/inspect/list 是实例级拓扑管理,只属于实例
|
||
authority Project Owner;
|
||
- RoleBinding 是 Project-scoped 权限管理,每个 Project 的当前 Owner 应能自治查询
|
||
自己 Project,而不能因此枚举其他 Project。
|
||
|
||
## 决策
|
||
|
||
### 1. 扩展既有 `ql3-policy`,不新增 package、migration 或常驻资源
|
||
|
||
在现有 project-policy-administration 四层 subpath 中新增:
|
||
|
||
- `policy.role-binding.inspect`;
|
||
- `policy.role-binding.list`。
|
||
|
||
查询只读取 v37 已有的 append-only RoleBinding 表并写已有 security audit 表。
|
||
workspace 保持 22 个 package,不新增依赖、daemon、timer、watcher、listener、缓存
|
||
或端口。
|
||
|
||
### 2. 每个 Project 的当前强认证 Owner 只能查询自己的 Project
|
||
|
||
local-admin 对 request `projectId` 固定请求 Owner-only `project.manage`。SQLite 在
|
||
同一个 `BEGIN IMMEDIATE` 事务内复用 put/revoke 的最终 authority helper,重新验证:
|
||
|
||
1. authenticated credential、Identity、有效期和 pepper provenance;
|
||
2. 目标 Project active/version;
|
||
3. actor 最新 active Owner RoleBinding/version。
|
||
|
||
验证后才读取目标或列表并原子写 allowed audit。admin/operator/viewer 和未绑定 User
|
||
均不能查询。secondary Project Owner 可以查询其自己的 Project,但不能把自身
|
||
`project.manage` 用于另一个 Project。
|
||
|
||
这条路径不要求 ADR-0211 的实例 authority anchor;错误地加入该要求会破坏
|
||
Project-scoped 自治。
|
||
|
||
### 3. inspect 只返回 target 的最新 revision
|
||
|
||
`policy.role-binding.inspect` 接受 Project、target、request ID 和单一 audit event
|
||
ID。SQLite 按 version 降序只取一条:
|
||
|
||
- 从未存在时返回 `found:false`;
|
||
- 当前 active 时返回 role;
|
||
- 当前 revoked 时不返回 role;
|
||
- 历史 active revision 不得覆盖最新 revoked revision。
|
||
|
||
成功输出只包含 Project、target、version、state、可选 role 和 created timestamp。
|
||
不返回 mutation ID、changedBy、credential、pepper、数据库路径或内部 audit。
|
||
|
||
### 4. list 只列出每个 subject 的最新 revision
|
||
|
||
`policy.role-binding.list` 要求:
|
||
|
||
- `limit` 为 1–64;
|
||
- `state` 为 `active|revoked|all`;
|
||
- `role` 为 `owner|admin|operator|viewer|all`;
|
||
- 可选 cursor 精确包含 `{subjectType, subjectId}`。
|
||
|
||
SQLite 先以相关子查询选择每个 `(projectId, subjectType, subjectId)` 的 max version,
|
||
再应用 state/role filter,按 `(subjectType ASC, subjectId ASC)` 查询 `limit + 1`。
|
||
只有确有下一条时才返回最后一条可见 binding 的 cursor。`role != all` 自然排除
|
||
role 为 null 的 revoked binding。
|
||
|
||
禁止返回历史 revision、offset、任意排序、模糊搜索、客户端 SQL 片段或无界数组。
|
||
|
||
每一页是独立授权、独立审计的 current-head snapshot,不是跨 command 的长事务。
|
||
翻页期间发生 put/revoke 时,要求严格同一时点清单的 operator 必须从第一页重查。
|
||
|
||
### 5. 查询与审计使用同一个最终事务
|
||
|
||
query command 不携带 mutation ID、expected version 或 failure-audit ID,只携带单一
|
||
audit event ID。成功查询与 allowed audit 原子提交;credential/Project/Owner fence
|
||
在服务授权后漂移时事务回滚,CLI 以同一 event ID 写低敏 denial audit。
|
||
|
||
## 不采用方案
|
||
|
||
### 直接暴露 `ProjectPolicyRepository.resolve`
|
||
|
||
拒绝。它只解析一个 subject 的 Policy snapshot,不提供 actor Owner authority、
|
||
credential fence 或同事务 audit,也会把内部 repository 变成产品 transport。
|
||
|
||
### 返回全部 append-only 历史
|
||
|
||
拒绝。产品 mutation 需要 current version,历史列表容易误判撤权状态且输出无界。
|
||
历史审计应由未来专门的 bounded audit/query contract 提供。
|
||
|
||
### 只允许实例 authority Project Owner 查询所有 RoleBinding
|
||
|
||
拒绝。这会破坏 Project-scoped RBAC 自治,并迫使 secondary Project 的日常成员管理
|
||
依赖实例根 Owner。
|
||
|
||
### 另建 RoleBinding query package 或管理 daemon
|
||
|
||
拒绝。查询与 mutation 共用 Project authority、SQLite、CLI 和交付闭包;拆包或常驻
|
||
服务增加供应链碎片和路由设备空闲成本。
|
||
|
||
## 影响
|
||
|
||
正向影响:
|
||
|
||
- put/revoke 的 expected version 不再要求直接 SQL;
|
||
- append-only 历史被明确折叠为每个 subject 的 current head;
|
||
- Project Owner 自治与实例拓扑 authority 保持分离;
|
||
- 64 条硬上限约束 CPU、内存和输出;
|
||
- put/revoke/query 共用同一个最终 Owner fence helper;
|
||
- SQLite contract 保持 v37,workspace 保持 22 包。
|
||
|
||
代价与限制:
|
||
|
||
- 每次查询写一条 security audit,并短暂取得 SQLite write reservation;
|
||
- 当前不提供历史 revision、RoleBinding approval 或 break-glass 恢复;
|
||
- Cluster 继续使用独立 PostgreSQL/RBAC management transport。
|
||
|
||
## 验证
|
||
|
||
- GitNexus:SQLite append method 1 个直接上游、CLI request normalizer 3 个上游,
|
||
repository/class/service factory 均为 LOW,未命中已索引执行流程;
|
||
- 真实 SQLite/Owner CLI 18/18:
|
||
- inspect 只返回 revoked current v3,不回退历史 active;
|
||
- missing 返回 `found:false`;
|
||
- 两页 keyset 覆盖四个最新 subject,无重复或静态遗漏;
|
||
- active viewer filter 只返回当前 viewer;
|
||
- secondary Project Owner 可查询自己的两个 Owner;
|
||
- secondary Owner 查询 foreign Project 被拒绝并写 denial audit;
|
||
- Project admin 查询被 `permission_missing` 拒绝;
|
||
- limit 65 在打开数据库前拒绝;
|
||
- credential 在服务授权后漂移时,最终 inspect 事务拒绝且不提交 allowed audit;
|
||
- Project lifecycle/query、RoleBinding mutation、防锁死和容量回归;
|
||
- local-admin 与 local-owner-cli strict TypeScript 通过;
|
||
- runtime-core/local-sqlite 完整 TypeScript 仍只受锁定依赖未物化影响,本切片未出现
|
||
类型错误;
|
||
- 不新增 migration、package、生产依赖或 deployment contract 版本。
|