5.3 KiB
ADR-0046: Cluster 两阶段安全 Admission
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFC:QL-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 中的 middleware,HTTP 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与 activeSecurityPrincipal;- authentication assurance;
allow | deny | require_approvalPolicy 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,固定执行:
- route resolver 产生稳定 operation、permission、Project scope 和 handler;
- authenticator 产生 active Principal;
- Policy authorizer 返回 decision/fence;
- 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 不可用返回各自稳定的
503code; - adapter 原始错误、credential、Principal、内部 Policy reason 和 stack 不进入 wire response;
alloweddecision 必须先完成安全审计,审计失败不得继续 handler。
HTTP 的通用 onError 仍只用于技术诊断,不能替代安全 decision fact。
4. 当前 pipeline 不是生产身份实现
本 ADR 只建立不可绕过的顺序与端口。生产启用仍需要:
- 真实 credential authenticator(panel/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。
验证
- runtime-core contract test 拒绝过期/未来 Principal、widened shape、非法 reason 与 fence。
- admission pipeline test 固定 route→authenticate→authorize→audit→handler 顺序。
- authentication reject、Policy deny、approval required、adapter failure 和 audit failure 都证明 handler 未执行。
- 真实 Node HTTP 测试只发送声明的
Content-Length而不发送 body,仍必须立即得到认证拒绝,证明 preflight 不等待 body。 - 公开 admission/config/application import closure 不得加载 legacy
back/**、PostgreSQL migration DDL 或 Drizzle schema。 - Node 24、依赖审计和 GitNexus impact/detect-changes 作为合并门禁。