Files
qinglong/docs/adr/ADR-0048-reviewed-cluster-route-registry.md
T

98 lines
5.5 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-0048: Cluster 受审 Route Registry 与授权输入边界
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-04、D-08、D-13、D-27 至 D-32、D-43 至 D-46
## 上下文
ADR-0046 已固定 route→Authentication→Policy→security audit→body→handlerADR-0047 也提供了 PostgreSQL Project Policy adapter。但 admission 当时仍接受任意 `ClusterControlRouteResolver`:resolver 可以在运行时根据未受审逻辑产生 operation、permission、Project ID 和 handler。类型检查不能证明该 resolver 没有从 query/header 自报 permission、依赖注册顺序选择重叠 route,或把 Project scope 延迟到 body 中决定。
对 cluster-control,这不是普通 router 的可替换实现细节,而是 Policy 的可信输入边界。路由表必须在 readiness 后开放流量之前完成验证;无权请求必须在低资源 edge gateway 或 cluster ingress 读取 body 前终结。
## 决策
### 1. Route definition 是启动时受审静态事实
`@qinglong/cluster-control/routes` 公开工厂,definition 必须显式提供:
- HTTP method 与 canonical `/api/v3/...` path template
- 全局唯一 operation ID
- Project Policy permission
- Project ID 对应的显式 path parameter,或明确的 `null` global scope
- query name allowlist
- domain handler。
operation、permission 和 Project scope 不得由 header、query、body 或 handler 返回值决定。`null` scope 在当前 Project Policy adapter 下默认 deny;未来全局 operation 必须使用独立受审 Policy,而不是绕过 Project Policy。
### 2. Registry 编译阶段拒绝歧义和无界配置
registry 在启动时:
- 最多接收 256 条 route、每条最多 16 个 segment 和 8 个 path parameter
- 禁止 wildcard、catch-all、可选 segment、尾随/重复斜杠和 percent-encoded template
- 拒绝相同 method 下可能匹配同一 path 的 static/parameter 重叠;
- 拒绝重复 operation ID、重复 parameter、未知字段和未声明的 Project parameter
- 固定最多 16 个 allowlisted query name。
拒绝重叠是为了让匹配结果不依赖注册顺序。新增 route 必须在 code review 中同时审阅 operation、permission、scope 和 handler。
### 3. Admission 只接受工厂产生的 Registry
admission 不再只做结构化鸭子类型检查。模块内部使用不可复制的 registry identity 验证 resolver;伪造相同 `contractVersion/resolve` shape 仍在装配时被拒绝。这样 application stack 不能静默注入临时 allow route 或 handler-owned resolver。
registry 仍返回原有 prepared route contract,使 Authentication、Policy、audit 和 HTTP body/drain 边界无需耦合具体业务 router 或框架。
### 4. 非规范请求在 Authentication 前拒绝
运行时匹配只接受安全 ASCII segment。以下输入返回稳定低敏 `400`,且不调用 authenticator、Policy、audit 或 handler
- percent-encoding path alias
- 重复/尾随斜杠、反斜杠或控制字符;
- 超长 path、过多 segment
- 未 allowlist query
- query 重复值、单值大小或控制字符超过硬边界。
canonical 但未注册的 method/path 返回 `route_not_found`,同样发生在 Authentication 前。
## 被否决的替代方案
### 继续接受任意 RouteResolver port
拒绝。可替换 resolver 对普通路由器是扩展点,对 Policy 输入却是 authority seam;任何调用方都可产生未受审 permission 或 Project scope。
### 复用 Express/Umi router metadata
拒绝。会把 cluster artifact 重新耦合到 2.x root/web framework,并且不能证明 middleware 注册、path normalization 与 Policy route 一一对应。
### 允许 wildcard 后按注册顺序选择
拒绝。新增 static route 可能被旧 wildcard 抢先匹配,使代码评审看到的 permission 与运行结果不同。
### 从 JSON body 提取 Project ID
拒绝。Policy 前读取 body 破坏两阶段 admission,也让攻击者先消耗解析内存。需要 Project scope 的 operation 必须把稳定 identity 放在 path 中。
## 影响
### 正向
- operation/permission/Project scope 从 handler 建议值提升为启动时验证的安全事实;
- 路由结果不依赖注册顺序或 URL 编码别名;
- 未知与畸形请求在认证和 body 读取前有界终结;
- registry package 入口不加载 legacy root、PostgreSQL migration DDL 或 Drizzle schema。
### 代价
- 动态插件不能直接向核心 `/api/v3` 注入任意 route;插件 action 必须经过独立 tool/action registry、Policy 和 manifest gate
- 同一 operation 的多个 HTTP alias 需要不同 operation ID 或显式重构,不能共享一个模糊审计身份;
- 当前 ADR 只完成安全 registry 机制;后续 ADR-0049 已补齐真实 credential authenticator 与持久化 audit sink,但 production business route、credential 管理面和 audit retention/query 仍未完成。
## 验证
1. registry contract test 覆盖 immutable scope、handler parameter、伪造 registry 拒绝和 source mutation 隔离。
2. configuration test 覆盖 widened shape、重复 operation、static/parameter overlap、无效 Project parameter 和 route 数量硬上限。
3. request test 覆盖 percent traversal、重复/尾随 slash、反斜杠、unknown query、重复 query bound 与 control character。
4. admission test 证明未知 route 不调用 authenticator,伪造 resolver 在 assembly 时失败。
5. Node 22/24、公开入口 import closure、cluster dependency audit 与 GitNexus impact/detect-changes 继续作为门禁。