4. 镜像供应链与制品流转
容器镜像从开发者提交代码到最终在运行时被拉取启动,中间会经过构建、推送、存储、扫描、签名、分发、拉取等多个环节。每一个环节都涉及不同的系统、不同的凭据和不同的信任假设。理解这条完整链路以及各环节之间的信任边界,是设计镜像安全策略的前提。
一、完整链路的七个关键环节
一条典型的镜像供应链可以拆解为以下环节:
- 源码与依赖获取:开发者从代码仓库拉取源码,构建工具从公共或私有包仓库下载依赖。此处的信任边界是“源码仓库和依赖仓库是否可信”。
- 镜像构建:在本地或 CI 环境中执行 Dockerfile,生成镜像层。构建环境本身可能被污染,基础镜像也可能携带漏洞。
- 镜像扫描与签名:构建完成后对镜像进行漏洞扫描、SBOM 生成和签名。扫描工具的数据库时效性和签名私钥的保护是信任关键点。
- 推送至镜像仓库:通过
docker push或 CI 流水线将镜像推送到 Registry。推送凭据的权限范围决定了谁能写入。 - 仓库存储与分发:Registry 存储镜像层和清单,可能配置复制策略分发到多个区域。仓库的访问控制和内容信任策略在此生效。
- 运行时拉取:Kubernetes 或容器运行时从 Registry 拉取镜像。拉取凭据、镜像引用方式(tag 还是 digest)直接影响安全性。
- 容器启动与运行:镜像解压为容器文件系统并启动进程。此时镜像内容已固化,但运行时配置仍可能引入风险。
这七个环节之间存在六个信任边界:源码到构建、构建到扫描、扫描到推送、推送到存储、存储到拉取、拉取到运行。每个边界都是攻击者可能切入的位置。
二、各信任边界的风险与防护要点
TB-1(源码/依赖 → 构建):最常见的风险是依赖投毒和基础镜像篡改。防护手段包括:锁定依赖版本(如 package-lock.json、go.sum)、使用私有代理仓库、对基础镜像固定 digest 而非 tag。例如,将 FROM node:18 改为 FROM node:18@sha256:abc123...,可确保每次构建使用完全相同的镜像层。
TB-2(构建 → 扫描):构建环境本身可能被污染,导致扫描结果不可信。建议在隔离的 CI Runner 中构建,构建完成后立即扫描,并将扫描结果与镜像 digest 绑定。若扫描在构建之后、推送之前执行,需确保扫描工具无法被构建脚本篡改。
TB-3(扫描 → 推送):签名私钥的保护是核心。私钥不应以明文形式存放在 CI 变量中,而应使用 KMS 或 HSM 托管。Cosign 支持 cosign sign --key kms://... 的方式引用外部密钥。同时,签名应覆盖镜像 digest,而非 tag。
TB-4(推送 → 存储):推送凭据通常具有仓库的写权限。应遵循最小权限原则,为每个 CI 流水线分配独立的机器人账号,仅允许推送到特定仓库路径。Registry 侧应开启不可变标签(immutable tags)或内容信任策略,防止已推送的镜像被覆盖。
TB-5(存储 → 拉取):这是最容易被忽视的边界。Kubernetes 默认使用 tag 拉取镜像,而 tag 是可变的。攻击者若获得仓库写权限,可将恶意镜像推送到相同 tag 下。防护手段包括:在 Pod 中使用 digest 引用镜像、启用 Admission Controller 校验镜像签名(如 Kyverno 或 OPA Gatekeeper 配合 Cosign)、配置 imagePullPolicy: Always 并配合 digest 使用。
TB-6(拉取 → 运行):镜像拉取后,运行时配置仍可能引入风险。例如,以 root 用户运行容器、挂载敏感宿主机路径、开启特权模式。这些风险不在镜像内容中,而在 Pod Spec 或容器运行时配置中。应通过 Pod Security Standards、SecurityContext 和 Admission Policy 进行约束。
三、用 digest 锁定镜像引用
在整条链路中,最有效的单一控制措施是使用 digest 而非 tag 引用镜像。以下是一个 Kubernetes Pod 示例:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
containers:
- name: app
image: registry.example.com/app@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
imagePullPolicy: IfNotPresent
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
使用 digest 后,即使仓库中的 tag 被覆盖,Kubelet 拉取的仍然是 digest 对应的确切镜像层。配合签名验证 Admission Controller,可以在拉取前拒绝未签名或签名不匹配的镜像。
四、信任边界的落地检查清单
| 边界 | 检查项 | 推荐做法 |
|---|---|---|
| TB-1 | 基础镜像是否固定 digest | 使用 image@sha256:... 引用 |
| TB-1 | 依赖是否锁定版本 | 提交 lock 文件,使用私有代理 |
| TB-2 | 构建环境是否隔离 | 使用临时 CI Runner,构建后销毁 |
| TB-3 | 签名私钥是否托管 | KMS/HSM 托管,禁止明文存储 |
| TB-4 | 推送凭据是否最小权限 | 按仓库路径分配机器人账号 |
| TB-5 | 拉取是否使用 digest | Pod Spec 中固定 digest |
| TB-5 | 是否启用签名验证 | Kyverno/OPA + Cosign 校验 |
| TB-6 | 容器是否非 root 运行 | 设置 runAsNonRoot: true |
| TB-6 | 是否限制特权与挂载 | 禁用 privileged,限制 hostPath |
整条镜像供应链的安全,不取决于某一个环节的强度,而取决于最薄弱的信任边界。将每个边界的控制措施落实为可验证的配置和策略,才能让镜像从构建到运行的全过程处于可信任状态。