「镜像来自哪里、里面装着什么、谁签的字?」——供应链攻击已成为容器安全的第一威胁:恶意镜像、被投毒的依赖、被篡改的构建产物。Docker 镜像供应链安全就是把"镜像"当作需要验证的软件工件:漏洞扫描 + SBOM 清单 + 数字签名 + SLSA 合规,让每个进入生产的镜像都可追溯、可证明、可信。
关键概念:供应链安全三件事——扫描(镜像里有没有已知漏洞)、SBOM(镜像里装了什么组件及版本)、签名(这个镜像是谁构建的、有没有被篡改)。三者合力,镜像从"信任来源"变为"可验证来源"。
1. 镜像供应链的威胁模型
1.1 攻击面在哪里
镜像供应链的攻击入口:
1. 基础镜像:官方镜像本身或被篡改(少见但致命)
2. 依赖投毒:npm/pip/apt 包被恶意替换(最常见)
3. 构建污染:CI 环境被攻破,构建产物被植入后门
4. Registry 篡改:镜像在仓库中被偷偷替换
5. 恶意镜像伪装:假冒官方镜像诱导拉取
后果:
一次投毒可污染所有使用该镜像的环境
→ 镜像必须"来源可验 + 内容可查 + 签名可证"
1.2 纵深防御的思路
防线一:扫描(漏洞已知与否)
防线二:SBOM(成分可见)
防线三:签名(身份与完整性)
防线四:准入(只有可信镜像能进集群)
防线五:运行时(最小权限、只读文件系统)
本指南聚焦防线一~四,运行时加固见 Docker 安全专题
2. 漏洞扫描与门禁
2.1 扫描工具
# Trivy(轻量、开源、全平台)
trivy image myapp:latest
# 输出:漏洞清单 + 严重等级 + 修复建议
# Snyk / Grype / Clair 同类的替代
# 也支持扫描 Dockerfile、依赖清单、IaC
2.2 扫描进 CI 门禁
# GitHub Actions:构建后立即扫描,高危阻断
- uses: aquasecurity/trivy-action@master
with:
image-ref: registry/myapp:latest
severity: CRITICAL,HIGH
exit-code: 1 # 发现高危 → 失败
ignore-unfixed: true # 无修复方案的可忽略(可选)
扫描策略:
- CI 每次构建都扫(最快发现)
- 定期全量扫(新 CVE 出现后补查)
- 高危/严重 → 阻断;中低危 → 限时修复
- 用固定版本基镜像(不可变 tag),减少漂移
ℹ️ 核心:扫描是"已知漏洞"的体检。它不能发现 0-day 与逻辑投毒,所以必须配合 SBOM 与签名做完整保障。
3. SBOM:软件物料清单
3.1 为什么需要 SBOM
SBOM(Software Bill of Materials)= 镜像的"成分表":
列出镜像内所有组件、版本、来源、许可证
价值:
- 新 CVE 爆出 → 秒查"我的镜像有没有这个组件"
- 合规审计 → 证明依赖透明、许可证合规
- 漏洞响应 → 快速定位受影响镜像与版本
3.2 生成 SBOM
# 用 syft 生成镜像的 SBOM
syft packages registry/myapp:latest -o cyclonedx-json > sbom.json
# 或 trivy 生成(内置)
trivy image --format cyclonedx --output sbom.json myapp:latest
# 把 SBOM 作为镜像的 attestation 一同存储/签名
# 与镜像一起进 Registry,随镜像分发
SBOM 最佳实践:
- 每次构建生成一份 SBOM,随镜像发布
- 用 CycloneDX / SPDX 标准格式
- 把 SBOM 签名并关联到镜像(见下)
- 扫描工具可用 SBOM 做"漏洞 → 成分"双向关联
4. 镜像签名:Cosign / Notation
4.1 签名解决什么
镜像在传输/存储中可能被篡改:
你拉取的 registry/myapp:v1 还是构建时的那个吗?
签名 = 对镜像 digest 做数字签名:
验证方(k8s/CI/拉取端)验签 → 确认镜像:
- 未被篡改(完整性)
- 来自可信签发者(身份)
- 是构建方所承认的产物(来源)
4.2 Cosign 签名与验证
# 生成密钥对
cosign generate-key-pair
# 签名并推送(签名作为一个 attestation 存进 registry)
cosign sign --key cosign.key registry/myapp:latest
# 验证
cosign verify \
--key cosign.pub \
registry/myapp:latest
# 输出 OK 才说明镜像可信
更强的做法:keyless 签名(无密钥管理)
cosign sign 用 OIDC 身份(GitHub Actions 等)
→ 证明"这个镜像确实由我们的 CI 构建"
→ 签名主体可绑定仓库/工作流,适合 CI/CD
4.3 签名在准入中的作用
签名 + 准入控制:
Kubernetes 用 admission webhook(如 cosigned/policy-controller)
拉取镜像前先验签,验不过则拒绝 Pod 创建
→ 从"拉取端自觉验证"升级为"集群强制准入"
5. SLSA:供应链完整性等级
5.1 SLSA 是什么
SLSA(Supply-chain Levels for Software Artifacts):
用等级(L1~L4)描述软件供应链可信度:
L1:构建有记录
L2:构建可审计(签名的构建日志)
L3:构建可复现、受控(隔离、防篡改)
L4:最高(完整溯源 + 可复现)
对 Docker:
- 构建过程可溯源(谁触发、什么提交、什么环境)
- 产物(镜像 + SBOM)被签名
- 构建环境受控、可复现
5.2 实践映射
把 SLSA 落地到 Docker 供应链:
- 用固定基镜像 + 锁定依赖版本(可复现)
- CI 构建记录 + 构建来源信息(生成 provenance attestation)
- 镜像 + SBOM + provenance 一并签名
- 构建环境隔离、不可信输入最小化
目标:至少达到 SLSA L2/L3,作为对外交付的可信凭证
6. 可信 Registry 与镜像准入
6.1 可信 Registry
自建 Registry(Harbor 等):
- 只从内部可信 Registry 拉镜像
- 禁止直连公网镜像(减少拉错/被投毒面)
- Harbor 支持:漏洞扫描、镜像复制、RBAC、
以及基于策略的"禁止携带高危漏洞镜像被拉取"
策略示例:
- 公网镜像必须"人工审查 + 转存到内网"后才可用
- 拉取端配镜像白名单/准入
6.2 运行时准入示例
# 用策略控制器(如 cosigned)对 Pod 做镜像验签准入
# 验证不通过 → Pod 创建被拒绝
完整链路:
构建(扫描 + SBOM + 签名)→ 内网 Registry(漏洞策略)
→ 集群准入(验签 + 只允许可信镜像)→ 运行时最小化
7. 常见避坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 只看扫描结果 | 0-day 漏过 | 叠加 SBOM + 签名 |
| 扫描不进门禁 | 高危镜像照样上线 | CI exit-code 阻断 |
| 不生成 SBOM | 新 CVE 无法快速排查 | syft/trivy 每构建生成 |
| 镜像无签名 | 被篡改无感知 | Cosign keyless 签名 |
| 直连公网拉镜像 | 引入未知风险 | 内网可信 Registry |
| 基镜像漂移 | 依赖版本不可控 | 固定 digest/版本 |
8. 最佳实践清单
□ 基镜像固定版本/digest,不用 latest
□ CI 每次构建跑漏洞扫描,高危阻断
□ 每构建生成 SBOM(CycloneDX/SPDX)并随镜像发布
□ 镜像用 Cosign(keyless)签名,关键环境强制验签
□ 构建产物附加 provenance(来源证明),对齐 SLSA L2/L3
□ 只从内网可信 Registry 拉取,禁止公网直连
□ 集群准入:验签 + 高危漏洞策略控制
□ 依赖与基础镜像持续追踪新 CVE,定期全量扫描
□ 最小镜像 + 非 root + 只读文件系统兜底
一句话原则
镜像供应链安全 = 扫描(已知漏洞)+ SBOM(成分可见)
+ 签名(来源可证)+ 准入(强制可信),缺一不可。
小结
Docker 镜像供应链安全的核心是把"镜像"当作要验证的软件工件:CI 构建时扫描 + 生成 SBOM,用 Cosign 签名证明来源与完整性,对齐 SLSA 等级提供可信凭证,最后用内网可信 Registry + 集群准入强制执行。落地记住五件事:基镜像固定版本、CI 扫描高危阻断、每构建生成 SBOM、镜像签名并准入验签、只从可信 Registry 拉取。当供应链的每一环都可验证、可证明、可追溯,容器化的最大优势——快速分发——才不会成为投毒与篡改的温床。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。