Files
qinglong/docs/adr/ADR-0046-cluster-two-phase-security-admission.md
T

102 lines
5.3 KiB
Markdown
Raw 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-0046: Cluster 两阶段安全 Admission
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-04、D-08、D-13、D-27 至 D-32、D-43、D-44
## 上下文
ADR-0045 建立了有界 HTTP surface,并保证 `/api/v3` 在 readiness 与 recovery 完成前不可达。但如果 Authentication 和 Policy 只是普通业务 handler 中的 middlewareHTTP adapter 仍会先读取、缓存并解析整个 JSON body。匿名或已撤权请求因此可以在拒绝前消耗内存和 CPU;新增 route 也可能因漏装 middleware 绕过统一 Policy/Approval/Audit。
cluster-control 还不能复用 legacy Express Controller 作为安全入口:它会把 2.x session、Sequelize、route scope 和控制面装配重新耦合到 cluster artifact,并破坏 edge/cluster 的 Profile 隔离。
## 决策
### 1. 共享安全契约属于 runtime-core
`@qinglong/runtime-core/security` 只定义并严格规范化:
- `SecuritySubject` 与 active `SecurityPrincipal`
- authentication assurance
- `allow | deny | require_approval` Policy decision
- 可选 Project/RoleBinding version fence
- 有界、低敏 reason code。
该入口没有数据库 driver、JWT library、HTTP framework 或 legacy 依赖。adapter 返回过期、未来时间、扩展 shape、非法 identifier、空 reason 或无效 fence 时视为安全依赖故障并 fail closed。
### 2. HTTP admission 固定为两个阶段
第一阶段 `prepare(metadata)` 只接收 method、path、query、有界 headers、request ID 和 AbortSignal,固定执行:
1. route resolver 产生稳定 operation、permission、Project scope 和 handler
2. authenticator 产生 active Principal
3. Policy authorizer 返回 decision/fence
4. security audit sink 记录低敏 decision fact。
只有全部成功且 decision 为 `allow` 时,pipeline 才返回 `PreparedAdmission`。HTTP adapter 随后才读取有界 JSON body,并调用 prepared operation。`deny``require_approval` 均不会读取 body 或调用业务 handler。
### 3. 安全依赖与审计 fail closed
- 无 credential 返回 `401 authentication_required`
- Policy deny 返回 `403 forbidden`
- 需要审批返回 `403 approval_required`
- route/authentication/authorization/audit 不可用返回各自稳定的 `503` code
- adapter 原始错误、credential、Principal、内部 Policy reason 和 stack 不进入 wire response
- `allowed` decision 必须先完成安全审计,审计失败不得继续 handler。
HTTP 的通用 `onError` 仍只用于技术诊断,不能替代安全 decision fact。
### 4. 当前 pipeline 不是生产身份实现
本 ADR 只建立不可绕过的顺序与端口。生产启用仍需要:
- 真实 credential authenticatorpanel/API App/mTLS 等必须分别建模);
- PostgreSQL Project/RoleBinding Policy adapter 与 version fence
- 经过评审的 route registry 和领域 handler
- 有界、持久化、可告警的安全审计 sink;
- ApprovalRequest/dispatch 对 `require_approval` 的产品入口。
禁止用 allow-all authenticator、固定 owner Principal 或无操作 audit sink 把当前 generic host 宣布为 production-ready。
## 被否决的替代方案
### 在每个 Controller 内手写鉴权
拒绝。无法证明所有 route 都经过相同顺序,且默认发生在 body 解析之后。
### 只依赖反向代理完成认证授权
拒绝。代理身份可以作为 credential transport,但领域 permission、Project fence、Approval 与审计仍必须由 QingLong 裁决。
### 认证成功后立即读取 body,再执行 Policy
拒绝。已认证不等于对目标 Project/operation 有权,撤权或 Agent approval 仍会让大 body 成为无意义资源消耗。
### 审计失败时继续业务请求
拒绝。允许动作却没有对应安全 decision fact 会破坏可追责性,并使安全存储故障静默降级。
## 影响
### 正向
- 未认证、撤权和需审批请求在 body 读取前被拒绝,保护低资源设备与 cluster gateway
- route 新增时必须显式提供 operation/permission/scope,不存在可选安全 middleware
- Principal/Policy contract 可被 edge、standalone、cluster-control 和 worker 复用;
- wire error 保持低敏、稳定且不绑定具体认证/数据库实现。
### 代价
- route resolver 必须能仅凭 method/path/query/headers 确定 permission 与 Project scope;依赖 body 才能授权的动作必须先重构为 path identity 或单独的受限 preview/admission 协议;
- 安全审计成为 availability dependency,需要有界队列、持久化和告警设计;
- 两阶段 handler contract 比普通 `(request) => response` 多一个 prepared operation。
## 验证
1. runtime-core contract test 拒绝过期/未来 Principal、widened shape、非法 reason 与 fence。
2. admission pipeline test 固定 route→authenticate→authorize→audit→handler 顺序。
3. authentication reject、Policy deny、approval required、adapter failure 和 audit failure 都证明 handler 未执行。
4. 真实 Node HTTP 测试只发送声明的 `Content-Length` 而不发送 body,仍必须立即得到认证拒绝,证明 preflight 不等待 body。
5. 公开 admission/config/application import closure 不得加载 legacy `back/**`、PostgreSQL migration DDL 或 Drizzle schema。
6. Node 24、依赖审计和 GitNexus impact/detect-changes 作为合并门禁。