Files
qinglong/docs/adr/ADR-0051-cluster-authentication-overload-shield.md
T

114 lines
7.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-0051: Cluster Authentication Overload Shield
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-05、D-35、D-40、D-43、D-44、D-47、D-50
## 上下文
ADR-0045 至 ADR-0049 已把 `/api/v3` 固定为有界 HTTP host,以及 route → Authentication → Policy → durable security audit → body → handler 的两阶段 admission。真实 bearer authentication 仍需要一次 PostgreSQL credential point lookup;攻击者不必持有合法 credential,就能用大量格式正确或错误的请求消耗连接池、审计写入和 CPU。仅靠 `maxInFlightRequests` 会在数据库工作已经开始后才体现压力,不能保护认证依赖。
QingLong 3.0 同时服务单副本、小规格集群和多副本控制面。把认证限流直接做成 PostgreSQL 或 Redis 权威配额会让滥用流量继续占用要保护的依赖,并为小集群增加新基础设施;只按 bearer/credential 限流则必须先查库,而且无法约束匿名攻击。反向代理部署还存在共享出口与伪造 forwarded header 的边界,不能默认把任意 `X-Forwarded-For` 当成身份。
## 决策
### 1. 在 transport admission 建立认证前资源护盾
cluster-control HTTP host 在以下检查之后启用护盾:
1. URL 有界且可解析;
2. liveness/readiness probe 已分流;
3. path 位于 `/api/v3` namespace
4. application admission 已完成安装。
护盾在 method validation、route registry、Authentication、PostgreSQL、durable security audit 和 body 读取之前消费预算。未 ready 请求不消耗预算,probe 永不受该预算影响。通过护盾不代表认证或授权成功;它只表示本副本当前愿意为该请求支付安全 admission 成本。
### 2. 每个副本使用双固定窗口与有界 peer 表
每个 cluster-control 进程维护:
- 每 transport peer 的固定窗口预算,默认 `300 / 60s`
- 本副本全局固定窗口预算,默认 `1200 / 60s`
- 最多 `4096` 个 peer 指纹;
- 每次新 peer 最多检查 `64` 个旧条目做惰性过期回收。
实现不创建 timer、后台任务、外部连接或无界集合。窗口、peer/global budget 和 peer 表均有启动配置硬上限;表已满且无法在有界扫描内回收时 fail closed。关闭 HTTP surface 时立即清空状态和指纹 key。
该状态刻意是 disposable、process-local 的 overload protection,不是跨副本安全事实、计费配额或公平调度权威。多副本总预算由部署副本数放大;需要全局业务 quota 时必须在已认证主体和 Project 上另建 durable policy,而不能把本护盾升级为隐式共享事实源。
### 3. 只信任 socket transport peer
peer key 只来自 Node TCP socket 的 `remoteAddress``Forwarded``X-Forwarded-For``X-Real-IP` 和任意业务 header 都不得影响首版限流;缺失、超长或含控制字符的地址进入同一个 fail-closed unknown bucket。
部署在 L4/L7 proxy 后时,所有请求可能共享 proxy peer budget。运维方可以提高 per-peer budget、依赖本副本 global budget,或降低每个 pod 的入口流量。未来若支持客户端地址传播,必须新增显式 trusted-proxy allowlist、固定 hop 解析和 spoofing contract,不能静默改变本 ADR 的信任边界。
### 4. 内存中不保存原始 peer 地址
进程启动时生成 32-byte 随机 key,对 domain-separated transport peer 做 HMAC-SHA-256,只以指纹作为 Map key。原始地址只在单次调用栈中短暂存在,不进入 event、日志、durable audit 或 wire response;关闭时清零 key。
### 5. 稳定 wire 与低基数观测
peer/global/capacity 超限统一返回 `429 authentication_rate_limited` 和整数秒 `Retry-After`。单调时钟异常或护盾不可用返回 `503 authentication_shield_unavailable`。护盾拒绝以及 route/Authentication/Policy/audit 等所有 pre-body 拒绝都固定关闭 HTTP/1.1 connection,避免未读取 body 或 pipeline 数据继续占用可复用 socket。响应不暴露 peer、窗口计数、credential existence 或具体部署容量。
HTTP surface 提供同步、不可改变 admission 结果的低基数 event hook,只包含:
- `outcome`: `rate_limited | unavailable`
- `reason`: `peer | global | capacity | clock`
hook 抛错会被吞掉,不能形成新的可用性依赖。transport 护盾拒绝发生在 Authentication 之前,因此不是 ADR-0049 的认证决策,也不为每个攻击请求写 PostgreSQL security audit;否则攻击者可以通过审计 INSERT 反向耗尽要保护的数据库。生产观测应把 hook 接入有界 counter,并在聚合阈值上告警。
### 6. Profile 与配置边界
配置只进入 cluster-control Profile
- `QL3_CLUSTER_AUTH_RATE_WINDOW_MS`
- `QL3_CLUSTER_AUTH_RATE_PER_PEER`
- `QL3_CLUSTER_AUTH_RATE_GLOBAL`
- `QL3_CLUSTER_AUTH_RATE_MAX_PEERS`
edge/standalone 不安装 cluster-control 包,也不承担该状态或 crypto/cluster 依赖。该护盾不替代 edge 本机登录、cluster-admin ceremony、人工 recovery、Worker enrollment 或业务 Project quota 各自的限流设计。
## 被否决的替代方案
### PostgreSQL/Redis 全局 token bucket
拒绝作为认证前护盾。匿名滥用仍会打到网络依赖和连接池,Redis 又成为小型部署的新强制依赖与可用性门禁。它可用于认证后的业务 quota,但职责不同。
### 只按 credential、subject 或 Project 限流
拒绝作为第一层。取得这些事实需要完成 token 解析和 PostgreSQL Authentication,无法保护认证路径本身;攻击者还可以持续发送不存在的 credential ID。
### 默认信任 `X-Forwarded-For`
拒绝。没有显式 trusted proxy 与 hop contract 时客户端可伪造无限 peer key,既绕过 per-peer budget,也耗尽 peer 表。
### 无界 sliding window、每 peer timer 或请求日志
拒绝。它们分别引入无界时间戳集合、大量 timer 或攻击者可控的日志写放大,不适合作为控制面资源保护边界。
## 影响
### 正向
- 匿名攻击在 body、credential lookup、Policy 和 audit INSERT 之前被限制;
- 不新增 PostgreSQL 表、Redis、timer 或跨副本一致性依赖;
- 内存和每请求回收工作有硬上限,peer 原始地址不保留;
- probe/readiness 和 admission drain 语义保持不变;
- edge/standalone 产物依赖方向不变。
### 代价与未完成项
- process-local 限制随副本数线性放大,不提供全局精确配额;
- proxy/NAT 后多个客户端共享 peer budget,需要显式调参;
- 固定窗口允许边界突发,首版以实现可审查和常数状态优先;
- 管理 API、人工 recovery、Worker enrollment、异常 credential 告警和认证后 Project quota 仍需各自策略;
- 需要在独立 cluster 镜像与真实入口代理下测量默认值,而不是把开发机默认值视为最终容量结论。
## 验证
1. 单元测试覆盖 peer/global/capacity、窗口回收、时钟回退和 dispose fail-closed。
2. HTTP 测试证明未 ready 请求与 probe 不消耗预算,超限在 admission/body 前返回。
3. HTTP 测试使用不同 `X-Forwarded-For` 仍命中同一 socket peer budget。
4. 配置测试覆盖四个环境变量的默认值和硬上限。
5. Node 22/24 contract、edge/cluster dependency audit 与 GitNexus detect-changes 继续作为合并门禁。