前置阅读:建议先阅读 Docker 镜像供应链安全:SBOM、签名、扫描与 SLSA 合规 与 Harbor 私有镜像仓库深度实践。前一篇从全景角度介绍供应链,本篇聚焦签名机制与验证落地。
关键概念:镜像签名解决的是**“这个镜像是不是我信任的团队构建并推送的”问题。签名本身不等于安全——必须配合验证策略**(谁签了才可信)与准入控制(不通过验证就不让拉取/运行)。现代签名的核心是 Sigstore:用短期证书(Fulcio)做 keyless 签名,把签名记录上链(Rekor)供审计。
1. 为什么镜像需要签名
1.1 镜像篡改的威胁模型
镜像在"构建 → 推送 → 拉取 → 运行"全链路都可能被篡改:
攻击面:
- 中间人篡改:HTTPS 到仓库连接被劫持(公网仓库、弱 CA)
- 仓库失陷:tag 被替换为带后门版本;依赖混淆:冒充官方包名
- 构建链投毒:CI 缓存/基础镜像被污染后重新发布
无签名的后果:拉取到"看起来同名"但 digest 不同的镜像,无人察觉
1.2 签名能防什么、不能防什么
| 威胁 | 签名是否有效 | 说明 |
|---|---|---|
| 镜像内容被篡改 | 有效 | digest 变化,签名校验失败 |
| tag 被替换到其他镜像 | 有效 | 签名不匹配该 digest |
| 构建过程被投毒 | 部分 | 需配合 SLSA provenance 记录构建输入 |
| 签名密钥泄露 | 失效 | 攻击者可用真密钥签名,须有密钥保护/轮换 |
| 镜像本身有漏洞 | 无效 | 签名只证"是谁发的",不证"没有漏洞" |
一句话:签名回答"是谁"(来源),扫描回答"有没有漏洞"(内容),两者互补不可替代。
2. Sigstore 与 Cosign
2.1 Sigstore 三大组件
| 组件 | 角色 | 作用 |
|---|---|---|
| Fulcio | 证书签发 | 为 OIDC 身份签发短期证书(证书里绑定邮箱/身份) |
| Rekor | 透明日志 | 记录签名条目,提供可审计的签名日志(防抵赖) |
| Cosign | 签名/验证 CLI | 生成签名、附加到镜像、校验签名 |
keyless 流程:
OIDC 身份(GitHub/Google 等)→ Fulcio 签发短期证书
→ Cosign 签名 → 签名写入镜像仓库(tag 或 Referrers)
→ 公钥哈希写入 Rekor → 验证时向 Rekor 查询
2.2 为什么 keyless 优于"一把私钥"
传统 PGP/私钥签名:私钥长期有效,泄露即全盘失效;分发轮换是负担
Sigstore keyless:
- 证书有效期短(约 10 分钟),身份来自 OIDC(绑定 CI/开发者)
- 无需管理长期私钥,泄露风险窗口极小
一句话:Sigstore 把"长期密钥管理"换成"短期证书 + OIDC 身份 + 透明日志",签名这件事变得可自动化且可审计。
3. OCI 镜像签名机制
3.1 签名存放在哪
签名是附加在镜像上的对象,有三种承载方式:
① 标签式(cosign 早期默认):ghcr.io/user/app:sha256-<digest>.sig
② OCI 1.1 Referrers(推荐):Index 通过 subject 字段关联,
用 ORAS/referrers API 读取
③ Notation/Notary v2(CNCF 生态):基于 OCI 1.1,x509 证书签名
3.2 Cosign 签名对象格式
# 拉镜像并用私钥签名(会产生 .sig 附件)
cosign sign --key cosign.key registry.example.com/app:v1
# keyless 签名(GitHub Actions 环境)
cosign sign --yes registry.example.com/app:v1
# 查看签名附件
cosign tree registry.example.com/app:v1
签名内容(payload):
- 被签名镜像的 digest(immutable 绑定)
- 签名者证书(含 OIDC 身份声明)+ 时间戳
- 可选自定义声明(如构建材料哈希)
3.3 Referrers 与 OCI 规范
# ORAS 推送任意 artifact(如 SBOM)关联到镜像 subject
oras attach --artifact-type application/spdx+json \
registry.example.com/app:v1 sbom.spdx.json
# 查看镜像的 referrers(签名、SBOM、漏洞报告)
oras discover registry.example.com/app:v1
一句话:OCI 1.1 的 Referrers 让"签名、SBOM、扫描报告"都挂在镜像 subject 上,成为可发现、可遍历的附件集合。
4. 签名工作流
4.1 本地与 CI 签名
# 生成密钥对(长期密钥方案,适合测试/私有)
cosign generate-key-pair
# 生产建议把公钥推送到 KMS/HashiCorp Vault
# GitHub Actions 内 keyless 签名(无需密钥)
cosign sign --yes $IMAGE
# GitHub Actions:构建 + 签名
jobs:
build:
permissions:
id-token: write # 关键:允许请求 OIDC token
steps:
- uses: sigstore/cosign-installer@v3
- run: docker build -t $IMAGE .
- run: docker push $IMAGE
- run: cosign sign --yes $IMAGE
4.2 签名 SLSA provenance
# 同时生成构建出处(provenance),防"构建输入被投毒"
steps:
- uses: slsa-framework/slsa-github-generator@v2
- run: cosign attest --predicate predicate.json --type slsaprovenance $IMAGE
4.3 签名注意事项
□ 只对 immutable digest 签名(v1 → sha256:...),tag 会漂移
□ 每次重建必须重新签名;签名与推送原子化(同一 CI job)
□ 多架构镜像:manifest list 需分别签每个平台清单
一句话:签名要"贴在 digest 上"而不是"贴在 tag 上",并在 CI 里构建/推送/签名一气呵成。
5. 签名验证策略
5.1 用 Cosign 验证
# 用公开公钥验证
cosign verify --key cosign.pub registry.example.com/app:v1
# keyless 验证(依赖 Rekor + 信任根)
cosign verify registry.example.com/app:v1 \
--certificate-identity-regexp ".*@example.com" \
--certificate-oidc-issuer-regexp ".*github.com"
# 验证并同时校验 SBOM attestation
cosign verify-attestation --type slsaprovenance registry.example.com/app:v1
5.2 验证策略文件
验证策略回答三个问题:谁签的、用什么签的、是否过期:
# cosign policy(示例:只信任指定签发身份)
registry.example.com:
images:
app:
- verified-by: example-corp
certificate-identity: "release-bot@example.com"
certificate-oidc-issuer: "https://github.com/login/oauth"
5.3 信任根与 CA
keyless 验证依赖公共信任根(Sigstore TUF 根):
- 离线/内网环境需要自建 Sigstore(fulcio+rekor+tuf 自托管)
- 或退回"私钥 + cosign.pub"方案,把公钥作为信任锚点
验证失败的表现:
- 签名不存在 / digest 不匹配 / 身份不符 / 证书已过期
一句话:验证策略是签名的"白名单"——没有策略约束的签名只是"有人签过",有策略约束才是"可信的人签过"。
6. 准入控制:让集群只跑可信镜像
6.1 Kyverno 校验镜像签名
# Kyverno ClusterPolicy:镜像必须通过 cosign 校验
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign
verifyImages:
- image: "registry.example.com/*"
key: |-
-----BEGIN PUBLIC KEY-----
...(信任锚点公钥)...
-----END PUBLIC KEY-----
6.2 Connaisseur 准入控制器
Connaisseur(基于 OCI 签名的准入控制器):
- 部署为 Validating Webhook,拦 Pod 创建
- 策略:require-all(所有镜像必须有有效签名)
- 支持 cosign 与 notaryv1 签名格式
- 验证通过才允许 Pod 调度(否则拒绝)
6.3 Docker 侧:Content Trust
# Docker Engine 开启内容信任(DCT)
export DOCKER_CONTENT_TRUST=1
docker pull registry.example.com/app:v1
# 无签名的镜像会被拒绝拉取
| 方案 | 控制点 | 强度 |
|---|---|---|
| DOCKER_CONTENT_TRUST | 拉取端(客户端) | 中(绕过容易) |
| Kyverno verifyImages | 集群准入(kube-apiserver) | 高(强制) |
| Connaisseur | 集群准入 Webhook | 高(强制) |
| Registry 策略 | 仓库侧(推送/拉取) | 中(与签名存储联动) |
一句话:验证最有效的着力点是集群准入——让不通过签名的镜像根本进不了 Pod,而不是依赖每个开发者的拉取习惯。
7. 与 SBOM / SLSA 联动
7.1 签名 SBOM 附件
# 生成 SBOM 并附加、签名
syft registry.example.com/app:v1 -o spdx-json > sbom.spdx.json
cosign attach sbom --sbom sbom.spdx.json registry.example.com/app:v1
cosign sign --key cosign.key registry.example.com/app:v1
cosign verify-attestation registry.example.com/app:v1 # 一并校验 SBOM 真实性
7.2 SLSA 等级与签名
SLSA L3 的核心要求之一就是"生成出处 + 签名出处":
签名对象 = SLSA provenance(构建器、输入、依赖清单)
消费端 = 部署准入时验证 provenance 的签名与内容
效果:
即使镜像本身被重建,出处中的构建输入也能被核对
7.3 完整供应链校验链
镜像拉取前依次校验:
① 镜像本身签名(谁构建的) cosign verify
② SBOM 附件签名(物料清单真实) verify-attestation
③ SLSA provenance(构建输入可信) verify slsaprovenance
④ 漏洞扫描报告(内容安全) Trivy/scan(见安全扫描篇)
一句话:签名、SBOM、SLSA 是供应链的"三道保险"——来源可验、物料可查、出处可溯,串起来才是完整的信任链。
8. 生产落地与常见坑
8.1 密钥管理
长期密钥方案:
- 私钥进 KMS/Vault,绝不进 CI 变量明文
- 公钥作为信任锚点发布到受控位置(如 GitHub 仓库 + cosign.pub)
keyless 方案:
- CI 角色需 OIDC federation 配置(GitHub OIDC Provider)
- 自建 Sigstore 时管理 Fulcio/Rekor/TUF 根
8.2 离线 / 内网 / 私有仓库
□ 私有仓库需支持 OCI 签名附件(Harbor 2.5+ 支持 cosign 附件)
□ 内网无 Rekor 访问 → 用私钥方案替代 keyless
□ 自建 Sigstore:需部署 fulcio、rekor-server、tuf 根、cosign
□ 离线验证:本地缓存 TUF 根与 Rekor 日志状态
8.3 轮换与撤销
# 证书/密钥轮换:旧签名将无法通过新公钥验证,需重新签名发布
# 撤销策略:
# - keyless 短证书:过期即失效,天然轮换
# - 长期密钥:泄露后用 cosign 吊销记录 + 重新签名所有受影响镜像
cosign verify --key old.pub image:v1 # 失败 → 触发重新签名流程
8.4 常见问题速查
| 症状 | 根因 | 对策 |
|---|---|---|
no matching signatures | 镜像未签名 | 补签并重新推送 |
| 验证通过但准入拒绝 | 策略身份不匹配 | 核对 certificate-identity 正则 |
| 私有仓库附件丢失 | 仓库不支持 Referrers | 升级 Harbor/注册 OCI 附件 |
| keyless 验证超时 | 无法访问 Rekor | 自建 Sigstore 或转私钥方案 |
| digest 变化后旧签名失效 | 重新构建未重签 | CI 内构建+签名原子化 |
一句话:签名落地最大的坑不是签名本身,而是"密钥/信任根/仓库兼容"这三个基础设施——先选好方案再上准入。
9. 总结
| 主题 | 关键结论 | 一句话记忆 |
|---|---|---|
| 威胁模型 | 签名防"谁"不防"漏洞" | 来源可验、内容靠扫描 |
| Sigstore | Fulcio 短证书 + Rekor 日志 | keyless 免密钥管理 |
| 签名对象 | 绑定 digest 而非 tag | 签 digest 不签 tag |
| 承载方式 | OCI 1.1 Referrers 附件 | 附件挂 subject |
| 验证策略 | 白名单身份 + 信任根 | 策略比签名重要 |
| 准入控制 | Kyverno/Connaisseur 强制 | 进不了 Pod 才算数 |
| SBOM 联动 | 签名 SBOM + SLSA provenance | 物料与出处可溯 |
| 生产落地 | KMS 管密钥、自建 Sigstore | 先选方案再上准入 |
镜像签名不是一次性的"加个命令",而是一套签名 → 存储 → 验证 → 准入 → 轮换的完整体系。落地要点:优先用 Sigstore keyless 在 CI 里对 digest 签名,把签名附件存进支持 OCI 1.1 的仓库,用 Kyverno/Connaisseur 在集群准入层强制验证,并让 SBOM 与 SLSA provenance 一并签名。当"无有效签名镜像无法运行"成为平台默认,供应链才算真正闭合。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。