Files
qinglong/docs/adr/ADR-0148-plugin-package-management-live-evidence-and-default-deny-egress.md
T

210 lines
11 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-0148Plugin Package 管理 Live Evidence 与默认拒绝 Egress
- 状态:Accepted(证据协议、采集器、审计器、默认拒绝 egress 与静态门已实现;真实报告待采集)
- 日期:2026-07-25
- 关联:ADR-0144 至 ADR-0147、QL-RFC-0001 D-142/D-143
## 背景
管理入口已经具备 TLS 1.3、外部强身份验证、双 User separation-of-duty、双副本清单、
durable quota 和全副本重启 keyset ledger,但这些单元/静态/本机 HA 证据不能替代:
- 真实外部 IdP 的两名不同 User;
- 三控制面、至少两工作节点的真实 Kubernetes
- CNI 实际执行的 ingress/egress NetworkPolicy
- 双 Pod 零不可用 keyset/TLS rotation
- manager role 对 Kubernetes Secret、executor mutation 和公网 egress 的真实否定。
CI 自签两枚 JWT 只能验证协议路径,不能被记录为真实 IdP 证据。仅看到两个 Ready Pod
也不能证明请求、轮换或网络隔离。
## 决策
### 1. 版本化低敏证据
新增 `qinglong/plugin-package-management-live-evidence@v1`
`ql3-plugin-package-management-live-evidence-audit.cjs`。报告最大 1 MiB,必须为绝对
路径上的 non-symlink regular file,且不可 group/world writable。
报告精确绑定:
- Kubernetes 精确 `gitVersion`(至少 1.32,允许受限的 managed-distribution
suffix)、架构、CNI 名称/版本、三控制面和至少两工作节点;
- management/PostgreSQL 实际 image ID
- 两个 Ready management replica、两个不同 Pod/Node identity 摘要、零 token mount
- `ql3_package_manager`、25 migration、capability v24、38 表;
- 非本机、非 IP、非保留测试域名的 canonical HTTPS OIDC issuer
- discovery/JWKS 摘要、两名不同 User subject 摘要和 MFA/hardware assurance
- 三个严格递增 keyset generation、最终 durable ledger generation 与 revoked 数量;
- propose、自批拒绝、另一 User approve、authorized inspect 和 durable Audit
- labelled/unlabelled ingress、错误端口、Kubernetes API/公网/PostgreSQL egress
- Secret read 与 executor mutation 权限否定;
- overlap、新 key、旧 key revoke、TLS serial/Secret generation 变化和 TLS 1.3
- 九个独立 summary gate 与总 `passed`
报告禁止出现 assertion、Authorization、bearer/token、password、Secret、DSN、
connection string、private key 或 JWT-like material。这个审计器验证报告契约,不凭空
生成事实;没有真实采集报告时,生产 gate 仍未通过。
### 2. 拒绝测试身份冒充生产证据
issuer 必须是 canonical HTTPS URL,并拒绝:
- localhost、`.localhost``.local`
- `.test``.invalid``.example`
- 任意 IP literal
- userinfo、query、fragment 或非规范 URL。
两名 User 的 subject 只记录 SHA-256 摘要但必须不同,assurance 只能是
`multi_factor``hardware`。CI fixture 可以继续用于回归,但不能通过 live evidence
审计。
### 3. 默认拒绝管理 Pod egress
基础 management NetworkPolicy 现在同时声明 `Ingress``Egress`
- ingress 只接受同命名空间带
`qinglong.io/plugin-package-management-client=true` 的客户端访问 TCP 8443
- base egress 只允许 kube-system `kube-dns` 的 UDP/TCP 53
- CloudNativePG overlay 只额外允许同命名空间
`cnpg.io/cluster=ql3-postgres` 的 TCP 5432
- 外部 PostgreSQL 或不同 DNS 实现必须在私有 overlay 精确声明目标,不能回退为 `{}`
namespace-wide 或公网 egress。
管理 Pod 不需要访问 IdP discovery/JWKS;受审 keyset 仍由独立部署 authority 投影,
避免把网络发现、watcher 或新 credential 放进公网 parser 进程。
### 4. 采集事实,而不是接受最终布尔值
新增 `ql3-plugin-package-management-live-evidence-collect.cjs`,但不新增 workspace
package、常驻进程或第三方依赖。它只由集群 operator 显式运行,edge/standalone
制品与运行时不导入。
采集器拒绝输入 `gates``passed` 等最终判断。它直接:
- 从显式 kubeconfig/context 查询 Kubernetes server、Ready/schedulable Node、
management Deployment/Service/ServiceAccount/Pod、CloudNativePG Pod、CNI DaemonSet
和实际 container image ID
-`kubectl auth can-i --as=system:serviceaccount:...` 验证 manager 不能读取 Secret
不能 create Job、patch Deployment 或 update ConfigMap
- 精确比较已部署的 CloudNativePG management NetworkPolicy,空 selector、额外 peer/
port、`ipBlock` 或其他宽化均拒绝;
- 仅把 `service=<name>` 交给 `psql`,通过 mode 0600 的 `PGSERVICEFILE`
`ql3_package_manager` 查询 PostgreSQL 18.4、25 migration、capability v24、38 表、
三条指定 durable Audit 和最终 keyset ledger
- 从外部 HTTPS issuer 获取 discovery/JWKS,禁止 redirect,限制每份 1 MiB,验证
discovery issuer 并对原始响应计算 SHA-256
- 将私有 exercise 中的 User subject、TLS serial 和 Secret resourceVersion 加域后
摘要化,不把原值、三个 Audit UUID 或 probe transcript 写入最终报告。
标准 API 无法重放的真实 HTTP 四眼、CNI 数据面探针和三阶段轮换仍由私有 exercise
记录原始 HTTP status/operation/error、probe outcome 与三份 readiness sample。该文件
必须是 canonical、non-symlink、最大 1 MiB、mode 0600,24 小时后过期,并且不得包含
assertion、credential 或预计算 gate。采集器从这些原始字段派生最终 boolean;这提高
了可复核性,但不能阻止拥有主机权限的人伪造输入,因此真实运行日志和变更记录仍属于
发布审计链。
### 5. 三阶段真实 exercise runner
新增 caller-driven `ql3-plugin-package-management-live-exercise.cjs`,将 raw exercise
拆为 `before → overlap → revoked → finalize`,不新增 workspace package、第三方依赖
或常驻进程:
- `before` 真实执行 requester propose、自批 403、reviewer approve/inspect,并用
tokenless、non-root、无 Secret 的临时 Pod 验证 labelled/unlabelled/错误端口 ingress
两个 management Pod 内分别验证 Kubernetes API/公网 deny 与 PostgreSQL allow
- operator 随后必须人工把 keyset 切到 old/new overlap、增加 generation,并轮换 TLS
Secret 后以零不可用 rollout 进入 `overlap`;同一 old/new assertion 都必须返回 200
- operator 再显式 revoke old kid、增加 generation且保持 overlap TLS,进入
`revoked`runner 要求 overlap 中完全相同的 old/new assertion,旧 assertion 在仍未
过期时返回 401,新 assertion 继续返回 200
- 每阶段写入一个新的 mode 0600、不可覆盖状态文件。状态只保存 assertion SHA-256
不保存 assertion 本身,并以 `previousStateSha256` 形成顺序链;该摘要链只证明连续
性,不是主机签名,拥有主机写权限者仍可重算,因此 operator 日志和发布变更记录仍是
信任边界;
- `finalize` 只接受三阶段严格递增时间/generation、三份双副本 TLS 1.3 readiness
sample 和五分钟内的 revoked 状态,再生成 collector 接受的私有 raw exercise。
runner 会写入一条专用 Package proposal/approval/Audit,但不会 consume、dispatch、
下载或激活 Package。`action-input` 必须是经过复核、无生产副作用的专用证据提案。
所有 assertion 文件、action input、kubeconfig 和阶段状态都必须是 canonical mode
0600 文件;临时探测 Pod 删除失败会使该阶段失败。
## 验证
- live evidence audit5/5
- live evidence collector7/7,覆盖真实对象派生、空 selector/策略宽化、manager-only
SQL、OIDC 原始摘要与 SSRF/流式大小边界、低敏输出与私有文件边界;
- live exercise runner6/6,覆盖强身份派生、私钥拒绝、三阶段摘要链、同 assertion
撤销、篡改/跳阶段拒绝、tokenless/non-root/有界 probe Pod 与仅清理本轮成功创建的
Pod
- 上述 collector/audit 在只读、无网络的 `node:24-bookworm-slim` 容器中合并复验
12/12
- cluster deployment audit`findings=[]`
- deployment audit tests:新增公网 egress 和宽化 PostgreSQL selector 负向门;
- management base/CloudNativePG Kustomize 均可 render
- 未新增 workspace package、第三方依赖、timer、watcher 或常驻进程。
## 尚未完成
必须由真实环境采集并通过:
```bash
export QL3_PLUGIN_PACKAGE_MANAGEMENT_LIVE_EXERCISE=1
pnpm evidence:plugin-package-management-live-exercise:ql3 -- before \
--kubeconfig=/absolute/private/kubeconfig \
--context=production \
--endpoint=https://management.example.org/api/v3/plugin-packages/management \
--ca-file=/absolute/private/management-ca.crt \
--requester-assertion=/absolute/private/requester.jwt \
--reviewer-assertion=/absolute/private/reviewer.jwt \
--action-input=/absolute/private/evidence-action.json \
--output-state=/absolute/private/management-before.json
# operator: old/new key overlap + generation increase + TLS Secret rotation +
# zero-unavailable rollout; old.jwt/new.jwt must remain valid through revoked.
pnpm evidence:plugin-package-management-live-exercise:ql3 -- overlap \
--kubeconfig=/absolute/private/kubeconfig \
--context=production \
--endpoint=https://management.example.org/api/v3/plugin-packages/management \
--ca-file=/absolute/private/management-ca.crt \
--input-state=/absolute/private/management-before.json \
--old-assertion=/absolute/private/old.jwt \
--new-assertion=/absolute/private/new.jwt \
--output-state=/absolute/private/management-overlap.json
# operator: explicitly revoke old kid + generation increase; keep overlap TLS.
pnpm evidence:plugin-package-management-live-exercise:ql3 -- revoked \
--kubeconfig=/absolute/private/kubeconfig \
--context=production \
--endpoint=https://management.example.org/api/v3/plugin-packages/management \
--ca-file=/absolute/private/management-ca.crt \
--input-state=/absolute/private/management-overlap.json \
--old-assertion=/absolute/private/old.jwt \
--new-assertion=/absolute/private/new.jwt \
--output-state=/absolute/private/management-revoked.json
unset QL3_PLUGIN_PACKAGE_MANAGEMENT_LIVE_EXERCISE
pnpm evidence:plugin-package-management-live-exercise:ql3 -- finalize \
--input-state=/absolute/private/management-revoked.json \
--output=/absolute/private/management-exercise.json
pnpm evidence:plugin-package-management-live:ql3 -- \
--kubeconfig=/absolute/private/kubeconfig \
--context=production \
--cni-daemonset=kube-system/cilium \
--cni-container=cilium-agent \
--cni-name=cilium \
--exercise=/absolute/private/management-exercise.json \
--pg-service-file=/absolute/private/pg_service.conf \
--pg-service=ql3_evidence \
--output=/absolute/private/plugin-package-management-live.json
pnpm audit:plugin-package-management-live-evidence:ql3 -- \
--report=/absolute/private/plugin-package-management-live.json
```
在报告不存在或审计失败时,真实 IdP 四眼、双 Pod ingress/rotation 与 Kubernetes
control-plane HA 仍是未完成状态,生产入口继续失败关闭。