软件供应链攻击日益频繁,镜像仓库一旦被攻破,攻击者可以替换标签对应的镜像内容。如果部署流程只依据镜像标签拉取,无法感知内容是否被篡改。镜像签名验证正是为了解决这一问题:发布者使用私钥对镜像摘要进行签名,使用者在拉取后通过公钥确认摘要与签名匹配,从而判断镜像是否来自可信来源。

一、镜像签名验证的核心原理
Docker 镜像在仓库中以清单文件描述层和配置信息。每个清单文件都有一个由内容计算得出的摘要,例如 sha256:abc123...。签名并不是对镜像所有层进行加密,而是对清单摘要进行私钥签名。验证时,客户端从仓库获取镜像清单、计算摘要,并使用发布者公钥验证附带的签名数据。如果摘要与签名不匹配,或者签名不存在,说明镜像可能被篡改或未经授权发布。
内容信任机制依赖非对称加密。私钥由发布者持有,绝不能泄露;公钥可以公开分发给使用者。Docker Content Trust 使用 Notary 项目管理信任元数据,Notary 在一个独立的服务中存储签名、密钥角色和过期时间等信息。客户端在启用内容信任后,不仅验证目标镜像的摘要签名,还会递归验证其依赖的基础镜像的信任关系,形成信任链。
这种设计可以防止中间人攻击和仓库内容替换,但并不能防止发布者自身发布恶意镜像。因此签名验证需要结合密钥管理和发布流程审计,确保私钥只被可信的 CI 系统或发布者使用。
二、使用 Docker Content Trust 验证签名
Docker Content Trust 是 Docker CLI 内置的功能,默认关闭。启用方式为设置环境变量 DOCKER_CONTENT_TRUST=1。在该模式下,docker pull、docker run、docker build 等命令都会执行签名验证。如果镜像没有签名或签名无效,命令会直接失败。
首次在客户端使用 DCT 拉取镜像时,Docker 会初始化本地信任目录,通常位于 ~/.docker/trust。如果发布者使用自签名密钥,客户端需要将发布者的公钥导入信任列表。对于 Docker Hub 上的官方镜像,Docker 会使用 Docker 的根密钥进行信任链验证。
下面示例展示启用 DCT 后拉取一个已签名镜像的过程。命令在终端中执行,输出为验证成功信息。
export DOCKER_CONTENT_TRUST=1 docker pull alpine:3.19
如果镜像没有签名或签名已过期,会看到类似下面的错误:
Error: remote trust data does not exist for docker.io/library/alpine: not found
推送镜像时,DCT 要求发布者输入密钥密码,并在推送成功后自动将签名元数据上传到 Notary 服务。发布者需要先创建签名密钥对,通常通过 docker trust 命令管理。DCT 的不足在于它依赖 Notary 服务端,自建 Notary 的部署和维护成本较高,而且对某些私有仓库的支持有限。
三、使用 Cosign 验证镜像签名
Cosign 是 Sigstore 社区推出的镜像签名工具,支持 OCI 注册表原生的签名存储方式。与 DCT 不同,Cosign 将签名作为单独的 OCI 工件推送到镜像所在仓库,不依赖额外的 Notary 服务。签名者可以使用传统密钥对,也可以使用无密钥模式,通过 OIDC 身份提供商完成短期签名。
使用 Cosign 前需要安装二进制文件。生成密钥对并签名镜像的命令如下:
cosign generate-key-pair cosign sign --key cosign.key myregistry.com/myapp:v1.0
上面的命令会在当前目录生成 cosign.key 和 cosign.pub。签名完成后,验证方只需要使用公钥验证:
cosign verify --key cosign.pub myregistry.com/myapp:v1.0
验证成功会输出镜像摘要和签名者信息。Cosign 还支持通过 verify-blob 验证任意制品,使用 verify-attestation 验证软件物料清单。它的优势在于不依赖中心化信任服务,兼容几乎所有支持 OCI 1.1 的注册表,因此在云原生生态中逐渐成为主流。
四、在 CI/CD 中强制执行验证
手动执行验证命令只能作为临时检查,真正有效的做法是把签名验证纳入持续集成和部署流水线。发布阶段由 CI 系统持有私钥完成签名,部署阶段在拉取镜像前用公钥验证。这样可以确保只有通过验证的镜像才能进入生产环境。
下面是一个在部署流水线中集成 Cosign 验证的 Bash 脚本示例:
#!/bin/bash IMAGE="$1" PUBLIC_KEY="cosign.pub" cosign verify --key "$PUBLIC_KEY" "$IMAGE" if [ $? -ne 0 ]; then echo "镜像签名验证失败,禁止部署" exit 1 fi docker pull "$IMAGE"
对于 Kubernetes 环境,可以通过准入控制器在创建 Pod 时校验镜像签名。例如使用 Sigstore Policy Controller 或 Open Policy Agent 编写策略,拒绝未签名或签名不匹配的镜像。这种架构下,即使开发者绕过了 CI 流程直接提交部署配置,集群也会阻止不安全的镜像运行。
需要注意的是,镜像标签可能被覆盖。如果使用可变标签如 latest,即使验证通过,后续同一标签可能指向不同镜像。因此建议在部署时使用镜像摘要而不是标签,或将标签设置为不可变。
五、常见问题与安全建议
镜像签名验证经常遇到两类问题:一是签名密钥管理不当导致私钥泄露,攻击者可以伪造签名;二是验证流程只覆盖了部分镜像入口,例如只验证了应用镜像却忽略基础镜像。私钥应存储在专用密钥管理系统中,例如云 KMS 或硬件安全模块,并定期轮换。
另一个常见误区是认为签名验证可以替代漏洞扫描。实际上签名只证明镜像来自可信发布者且未被篡改,并不保证镜像内部没有已知漏洞。签名验证应作为供应链安全的第一道关,后续仍需进行漏洞扫描、配置审计和运行时监控。
在网络隔离环境中,无法访问公网信任服务时,需要提前将公钥和签名元数据同步到内网。Cosign 可以将签名工件复制到内网仓库,而 DCT 则需要部署内网 Notary 服务。选择方案时应结合团队规模、仓库类型和运维能力。
下表对比 DCT 与 Cosign 在几个维度上的差异:
| 维度 | Docker Content Trust | Cosign |
|---|---|---|
| 签名存储 | Notary 服务 | OCI 工件 |
| 依赖组件 | Notary 服务器、数据库 | 兼容 OCI 的注册表 |
| 密钥模式 | 私钥+角色委派 | 传统密钥或无密钥 |
| 验证方式 | Docker CLI 内置 | cosign 命令或 SDK |
| 适用场景 | Docker 生态早期用户 | 云原生、Kubernetes、供应链安全 |
综合来看,新项目优先选择 Cosign 会更灵活,而仍在大量使用 Docker CLI 和 Docker Hub 官方镜像的团队可以继续使用 DCT,但需要评估 Notary 服务的维护成本。
Docker镜像签名签名验证镜像供应链安全修改时间:2026-09-07 18:31:47