mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
152 lines
6.9 KiB
Markdown
152 lines
6.9 KiB
Markdown
# ADR-0215:实例 Owner 围栏化、有界的 Local Security Audit 查询
|
||
|
||
- 状态: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-201、D-203、D-204、D-205
|
||
- 关联 ADR:ADR-0049、ADR-0074、ADR-0208、ADR-0211、ADR-0213、ADR-0214
|
||
|
||
## 背景
|
||
|
||
QingLong 3.0 的本机管理入口已经为 Secret、Identity、Credential、Project 和
|
||
RoleBinding 的允许、拒绝与最终围栏失败写入 durable security audit,但部署者仍没有
|
||
受支持的读取入口。直接查询 SQLite 会把内部 schema 变成产品 API,绕过强认证、
|
||
authority、字段脱敏和有界资源契约。
|
||
|
||
审计记录横跨整个实例,可能包含 Project 拓扑、subject、操作结果和认证关联信息。
|
||
它不是普通的 Project-scoped 业务数据,任意 secondary Project Owner 不应因为拥有
|
||
自己的 `project.manage` 就能枚举其他 Project 的安全事件。
|
||
|
||
## 决策
|
||
|
||
### 1. 在既有 package 内增加独立 subpath 和短生命周期 CLI
|
||
|
||
新增:
|
||
|
||
- `@qinglong/runtime-core/local-security-audit-query`;
|
||
- `@qinglong/local-sqlite/security-audit-query`;
|
||
- `@qinglong/local-admin/security-audit-query`;
|
||
- `@qinglong/local-owner-cli/security-audit-query-command`;
|
||
- 一次命令、一次进程的 `ql3-audit` binary。
|
||
|
||
这些是已有 package 内的能力边界,不是新的 workspace package。查询不进入常驻
|
||
application,不创建 daemon、timer、watcher、listener、连接池、缓存或端口。SQLite
|
||
contract 保持 v37,workspace 保持 22 个 package。
|
||
|
||
### 2. 只有实例 authority Project 的当前强认证 User Owner 可以查询
|
||
|
||
request 必须携带 `authorityProjectId`。local-admin 固定以 `project.manage` 请求
|
||
Owner-only decision;SQLite 在同一个 `BEGIN IMMEDIATE` 内重新验证:
|
||
|
||
1. authenticated credential、Identity、有效期和 pepper provenance;
|
||
2. ADR-0211 instance authority anchor;
|
||
3. authority Project 的 active/version;
|
||
4. actor 最新 active Owner RoleBinding/version。
|
||
|
||
secondary Project Owner、admin/operator/viewer、非 User principal 和 foreign
|
||
authority Project 全部失败关闭。服务授权后 credential、anchor、Project 或
|
||
RoleBinding 漂移时,最终事务拒绝且不得提交 allowed audit。
|
||
|
||
### 3. 查询形状固定且有界
|
||
|
||
`security.audit.list` 复用共享 security audit query 的精确语义:
|
||
|
||
- `limit` 为 1–64;共享 Cluster 上限 200 不改变本机更严格上限;
|
||
- filter 只允许可选 `projectId`、精确 `subject`、精确 `outcome`;
|
||
- cursor 精确为 `{occurredAtMs,eventId}`;
|
||
- 排序固定为 `(occurredAtMs DESC,eventId DESC)`;
|
||
- SQLite 读取 `limit + 1`,只有实际存在额外记录才返回 `nextCursor`。
|
||
|
||
禁止 offset、任意排序、模糊搜索、调用方 SQL、无界数组或历史全量 export。每一页是
|
||
一次独立授权、独立审计的 SQLite snapshot,不承诺跨 command 的同一时点视图。
|
||
|
||
### 4. 查询与自己的 allowed audit 原子提交
|
||
|
||
SQLite 先在事务内完成 authority 复验和查询,再写本次 `security.audit.list` allowed
|
||
audit,最后 commit。因此返回的 snapshot 不包含本次查询事件;查询成功和审计写入
|
||
仍保持原子性。
|
||
|
||
翻页必须使用新的 request ID 和 audit event ID。查询不是 mutation receipt;不得以
|
||
重放旧 audit event ID 的方式请求下一页。
|
||
|
||
### 5. 产品输出必须脱敏
|
||
|
||
`ql3-audit` 只输出:
|
||
|
||
- event/request/operation identity;
|
||
- nullable Project;
|
||
- nullable subject;
|
||
- outcome、reasons、nullable policy fence;
|
||
- occurred timestamp;
|
||
- nullable next cursor。
|
||
|
||
即使 durable row 中存在,也不得输出 `authenticationId`。同样不得输出 credential、
|
||
pepper、Secret、路径、command 内容或数据库内部字段。认证失败和最终围栏失败只写
|
||
低敏 failure audit。
|
||
|
||
## 不采用方案
|
||
|
||
### 直接开放只读 SQLite 或通用 SQL
|
||
|
||
拒绝。它无法约束字段、行数、排序、authority 或审计,并把 schema 演进变成外部兼容
|
||
承诺。
|
||
|
||
### 让每个 Project Owner 查询自己 Project 的原始审计
|
||
|
||
拒绝。当前 security audit 还包含 nullable/global 和实例管理事件,没有独立的
|
||
Project 安全视图、行级投影或字段级 redaction contract。需要时应另行设计
|
||
Project-local projection,而不是弱化实例审计 authority。
|
||
|
||
### 一次返回全部记录或实现 export
|
||
|
||
拒绝。Edge 路由设备的内存、闪存和终端输出必须有硬上限。合规 export、签名、保留期
|
||
和销毁证明是后续独立能力,不能伪装成大页查询。
|
||
|
||
### 新建 audit package 或常驻管理服务
|
||
|
||
拒绝。该能力与既有 runtime contract、单 SQLite authority、local-admin 和 Owner CLI
|
||
共享部署生命周期;新 package/daemon 只会增加低配设备的供应链和空闲成本。
|
||
|
||
## 影响
|
||
|
||
正向影响:
|
||
|
||
- 部署者不再需要直接读取 SQLite 才能诊断拒绝和围栏失败;
|
||
- 实例级审计 authority 与 Project-scoped RoleBinding authority 明确分离;
|
||
- 64 条硬上限、keyset 和精确 filter 约束内存、CPU 与输出;
|
||
- 同事务 final fence 防止 credential/Policy TOCTOU;
|
||
- CLI 删除 `authenticationId`,避免认证关联标识进入终端或脚本输出;
|
||
- 不新增 migration、package、生产依赖或常驻资源。
|
||
|
||
代价与限制:
|
||
|
||
- 每页查询都会短暂取得 SQLite write reservation,以原子写查询审计;
|
||
- 当前不提供 operation filter、时间范围、聚合、export、retention、签名或告警;
|
||
- 每页是独立 snapshot,翻页期间的新事件可能改变后续结果;
|
||
- Cluster 继续使用独立 PostgreSQL management authority 和更高的共享页上限。
|
||
|
||
## 验证
|
||
|
||
- GitNexus:共享 `normalizeSecurityAuditQuery` 上游风险 LOW;高风险
|
||
`LocalSqliteRunRepository`(17 个直接、45 个总上游)未修改,D-205 使用独立
|
||
repository/composition;
|
||
- D-205 五个新源文件通过隔离 strict TypeScript,启用
|
||
`exactOptionalPropertyTypes` 与 `noUncheckedIndexedAccess`;
|
||
- 定向测试 8/8:
|
||
- instance Owner 授权与 filter/cursor 透传;
|
||
- limit 65 在 repository 前拒绝;
|
||
- non-Owner denial audit;
|
||
- service 后 final policy/credential fence conflict 保留;
|
||
- 真实 SQLite `limit + 1`、两页降序 keyset 和末页 null cursor;
|
||
- query snapshot 不包含自己的 allowed audit;
|
||
- foreign instance authority 在读取/allowed audit 前拒绝;
|
||
- CLI 删除 `authenticationId` 并审计 credential fence failure;
|
||
- SQLite contract 保持 v37,workspace 保持 22 package;
|
||
- 完整 package closure 与 PostgreSQL HA Docker 门仍需在锁定依赖物化后重跑。
|
||
|
||
## 后续状态
|
||
|
||
ADR-0216 已在 SQLite v38 完成实例 Owner 围栏化、64/512 条硬上限的诊断审计压缩。
|
||
它只回收无已知引用的拒绝/失败事件和精确只读查询事件;完整领域 retention、签名
|
||
export、远端归档与合规销毁证明仍未完成。
|