3. 制品与镜像安全基线
在云原生供应链中,镜像不是“一个文件”,而是一组可寻址、可验证、可追溯的制品集合。理解镜像分层、签名、SBOM 与 provenance 四者的关系,是建立生产级制品可信准入的前提。本节从镜像的物理结构出发,逐步过渡到“如何证明这个镜像可信、由谁构建、包含什么”,最后落到准入控制的基本要求。
镜像分层:内容寻址的不可变结构
OCI 镜像由 manifest、config 和一组 layer 组成。每个 layer 是一个 tar 归档,通过其内容的 SHA-256 摘要寻址,因此 layer 天然不可变——内容变一位,摘要全变。manifest 记录 config 与各 layer 的 digest 及顺序,config 记录环境变量、入口命令、暴露端口等运行时元数据。镜像的“标签”(如 app:1.4.2)只是指向 manifest digest 的可变指针,真正可信的引用必须落到 digest,例如 registry.example.com/app@sha256:9f2c…。
分层带来两个工程后果。其一,共享基础层可显著降低存储与传输成本,但共享也意味着漏洞会跨镜像传播:若 debian:12-slim 的某一层含 CVE,所有基于它的镜像都受影响。其二,构建时的层缓存会掩盖依赖变化,导致“同一 Dockerfile 两次构建产出不同 digest”。因此生产基线要求:部署引用 digest 而非 tag,并在 CI 中固定基础镜像 digest,避免上游 tag 漂移。
签名:证明“这个 digest 是我发布的”
签名解决的是来源真实性与完整性。当前主流做法是 Sigstore 的 keyless 签名:CI 系统通过 OIDC 身份向 Fulcio 申请短期证书,用其私钥对镜像 digest 签名,签名与证书写入 Rekor 透明日志,形成可公开审计的记录。验证方只需信任 Fulcio 根与 Rekor 公钥,即可校验“该 digest 由某仓库某工作流在某时间签署”。
签名对象必须是 digest,而非 tag。若对 tag 签名,攻击者可将 tag 重新指向恶意 digest 而签名仍“有效”。验证策略通常写成:cosign verify registry.example.com/app@sha256:9f2c… --certificate-identity-regexp '^https://github.com/org/repo/.github/workflows/release.yml@refs/tags/v.*' --certificate-oidc-issuer https://token.actions.githubusercontent.com。这条命令同时约束了身份与签发者,缺一不可。
SBOM:镜像里到底有什么
SBOM(软件物料清单)回答“这个镜像包含哪些组件、版本、许可证与依赖关系”。生产上常用 SPDX 或 CycloneDX 格式,由 Syft、Trivy 等工具在构建后生成,并与镜像 digest 绑定存储。SBOM 的价值在于把“事后扫描”变成“可查询的资产账本”:当某个库爆出 0day 时,可直接用 SBOM 反查哪些镜像受影响,而不必重新拉取扫描全部镜像。
需要注意,SBOM 的准确性取决于生成时机与工具覆盖范围。仅扫描最终镜像可能漏掉构建期依赖;仅扫描源码则可能漏掉基础镜像中的系统包。较稳妥的做法是“构建期生成 + 镜像内嵌或旁路存储 + 与 digest 关联”,并在准入时校验 SBOM 是否存在且 digest 匹配。
Provenance:证明“它是怎么被构建出来的”
Provenance(来源证明)描述构建过程:源码仓库与 commit、构建平台、构建参数、输入制品 digest、输出镜像 digest。SLSA 框架将其分为多个等级,SLSA Level 3 要求构建在受控、隔离、可审计的平台完成,并生成不可伪造的 provenance。它与 SBOM 互补:SBOM 说“里面有什么”,provenance 说“它从哪来、怎么来的”。
在准入判断中,provenance 可用于拒绝“本地手工构建后推送到生产仓库”的镜像——因为其 builder 不是受信 CI,或缺少可验证的签名链。典型校验包括:builder ID 是否在白名单、source repo 是否匹配、buildType 是否符合预期、materials 中的基础镜像 digest 是否来自受信仓库。
生产环境制品可信准入的基本要求
把上述四类证据落到准入控制,核心是“先验证、后放行”。Kubernetes 中通常由 Admission Webhook(如 Kyverno、OPA Gatekeeper)在 Pod 创建前拦截,校验镜像引用是否为 digest、签名是否由受信身份签发、SBOM 与 provenance 是否存在且匹配、扫描结果是否满足阈值。以下是一份可直接参考的策略要点清单:
| 校验项 | 要求 | 不满足时的处置 |
|---|---|---|
| 镜像引用 | 必须使用 @sha256: digest,禁止裸 tag | 拒绝 |
| 签名 | digest 有有效签名,identity 与 issuer 匹配白名单 | 拒绝 |
| SBOM | 存在且与 digest 绑定,格式为 SPDX/CycloneDX | 拒绝或告警 |
| Provenance | builder 在受信列表,source repo 与 commit 可追溯 | 拒绝 |
| 漏洞 | 无 Critical,High 需有豁免记录与到期时间 | 拒绝或人工审批 |
| 基础镜像 | 来自受信仓库且 digest 固定 | 拒绝 |
常见误区:只校验 tag 不校验 digest;只验签名不验身份(任何有效签名都放行);SBOM 生成后不与 digest 绑定,导致“张冠李戴”;provenance 只检查存在性而不校验 builder 与 source。这些都会让准入形同虚设。
落地顺序与验证方法
建议按“先观测、后拦截”推进:第一阶段在 CI 中生成签名、SBOM、provenance 并推送到仓库,准入侧仅记录不合规镜像,观察存量影响面;第二阶段对新增命名空间启用拒绝策略,白名单放行历史镜像;第三阶段全量收紧,并定期用 cosign verify、trivy image、cosign verify-attestation 做抽样复核。验证时重点观察三类信号:准入日志中拒绝原因分布、豁免记录的数量与到期情况、以及同一 digest 在多个环境中的证据一致性。只有当“拒绝原因可解释、豁免可收敛、证据可复现”时,才说明基线真正生效。
一句话总结:镜像分层决定“引用什么”,签名决定“谁发布的”,SBOM 决定“里面有什么”,provenance 决定“怎么构建的”;准入控制则是把这四问变成可执行、可审计的放行条件。