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

5.3 KiB
Raw Blame History

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。denyrequire_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 作为合并门禁。