镜像签名与供应链安全实战:用 Sigstore/cosign 让镜像可验证可追溯


自托管用久了,你从各个来源(官方、GitHub、别人 Docker Hub 拉下来的镜像)拉了一堆容器镜像,心里其实一直悬着一个问题:"这张镜像到底是不是我想要的?有没有被偷偷换过、注入过恶意代码?"镜像供应链攻击(比如供应链投毒、镜像仓库被攻破、镜像被篡改)正是这几年的高危话题。镜像签名(Image Signing)与供应链验证回答的正是这个:让镜像在构建后被签名、在部署时被验证,确保你跑的就是"签名方认可的那个镜像"。这篇讲清签名解决什么、Sigstore/cosign 怎么用、怎么在写 CI 时签名、部署时校验,以及常见坑与边界。

一、镜像签名解决什么:让"我拉的镜像"可验证、可信、可追溯

软链路的信任链条很容易断:你从 Docker Hub 拉一个镜像,但没法证明"这就是发布方构建并发布的那一个",因为它只是"按名字拉到的字节"。中间人、仓库被篡改、同名替换、覆盖 tag,都可能让你跑在一个"跟你以为的完全不同的镜像"上。签名就是给"镜像内容"加一道可验证的背书:发布方在构建后用私钥对镜像(的 digest)签名,把签名公开;你在部署时用对应的公钥/信任根校验签名,确认镜像内容没被改动、且确实来自被信任方——这就把"拉到的就是原来的"从"靠名字猜"变成"可密码学验证"。

这属于"软件供应链安全"的一部分:不只是镜像内容本身,还包括"它依赖的基础镜像、构建时用的代码、发布者身份"是否可信。签名解决其中最关键的"镜像没被篡改 + 确实来自签名方"一环,是供应链可追溯与可审计的基础。

二、Sigstore / cosign 怎么用:构建后签名、部署前校验

相较过去繁琐的"自建 CA 管理密钥"式签名,Sigstore 走的是"免密钥(keyless)+ 短期证书"的更现代化路线:构建者在构建时通过身份(如 OIDC/邮箱/CI provider)临时获取一个短期证书来签名,证书与签名一并存到透明日志(Rekor);校验方根据你声明的身份(如邮箱/仓库)而非长期私钥去校验。命令行工具是 cosign。流程两端收拢成清晰的"两步":发布方构建完签名并推送签名(含摘要),部署方在 pull 前用 cosign verify 校验签名与身份是否匹配。

# 1) 构建并推送镜像后,用 cosign 签名(keyless,签发短期证书)
cosign sign --yes ghcr.io/myorg/myapp:v1.2.0

# 2) 部署前校验:确认镜像确实由"该身份"签名、且摘要匹配
cosign verify ghcr.io/myorg/myapp:v1.2.0 \
  --certificate-identity-regexp ".*@myorg\.com" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

# 3) (可选)也给镜像的数字指纹/摘要本身验证
docker manifest inspect ghcr.io/myorg/myapp:v1.2.0   # 看 digest

关键认知:签名的对象是"镜像摘要(digest)"而不是"tag"。tag 会被覆盖、指向别的 digest,而 digest 是不可变的内容指纹。所以校验签名时要用"被签名的 digest"去拉取,而不是凭 tag——这也顺带解释了为什么生产部署最好锁 digest(`image@sha256:...`),让 tag 覆盖无论如何也不影响你跑的内容。

三、把签名嵌进 CI、把校验接进部署:让供应链安全自动化

签名/校验要真发生作用,就得"自动"嵌进流程,而不是靠人肉记得:在 CI/CD 里,构建步骤结束后自动执行 cosign sign(用 CI 提供的 OIDC 身份);在部署侧,拉镜像前自动执行 cosign verify + enforce policy,不通过就 fail 根本不让起。用策略引擎(如 Sigstore 的 policy-controller / Kyverno)可在 Kubernetes 上强制"未签名镜像不许运行",把这些从"最佳实践"变成"平台强制约束"。一旦自动化,供应链安全就从"想起来配"变成"每次构建像签收件一样自然发生"。

# GitHub Actions 示例:构建推送后,用一个带 OIDC 的步骤自动签名
- name: Sign image
  run: cosign sign --yes --oidc-issuer https://token.actions.githubusercontent.com \
        ghcr.io/myorg/myapp:${GITHUB_SHA}
  env:
    COSIGN_EXPERIMENTAL: 1   # 启用 keyless 短期证书

# 部署侧强制(Kubernetes + Kyverno 策略示意):
#   对"拉取的镜像",先 cosign 校验,未签名/签名身份不符 → 拒绝创建 Pod

自动化的好处是把"信任链"绑定到"工作流"上:默认安全、异常即断。别让签名成为"偶尔做一下的仪式",它要么全自动化生效,要么等于没用——因为人工往往会在忙的时候跳过。

四、常见坑:签名对象搞错、依赖不签、透明日志与回放、密钥管理

签名落地,四个坑要防。一是"签了 tag 没签 digest,且部署不锁 digest":tag 可被覆盖,等于签名形同虚设——签名 + 锁 digest 一起做才有效。二是"只签自己的镜像,基础镜像/依赖不验证":底层基础镜像若被投毒,你的签名只证明"你的层"没改。要结合对基础镜像的校验与 SBOM(软件物料清单)审计。三是"透明日志与密钥管理":keyless 依赖 Rekor 透明日志与 OIDC 及时性,网络不通、时间对不上可能校验失败;传统私钥方案则要保护私钥(HashiCorp Vault/安全硬件),别把私钥摆在源码里。四是"回放/时间":校验时注意证书有效期与签名时间,防止签了过期的旧镜像仍被当作可信。

# 校验失败的常见原因排查
#   cosign verify 报 "no matching signatures":
#     - 该镜像tag从未签名 / 签名身份与你声明的 identity 不符
#     - 你拉的是另一个 digest(tag被覆盖),先看 digest 是否一致
# 用 SBOM 补基础镜像审计:
#   cosign download sbom ghcr.io/myorg/myapp:v1.2.0

把这当排障 SOP:先确认"拉的就是被签名的那个 digest",再确认"校验的身份/issuer 对应你信任的发布方",再看"证书是否在当前有效期、是否需要访问 Rekor"。签名校验失败时,宁可拒绝启动也别"关掉校验先让服务跑起来"——那是把刚刚加固的信任链又亲手拆掉。

五、边界:签名保"来源与完整性",不是"镜像里没有漏洞"

镜像签名的边界要看清:它证明的是"这个镜像就是这个签名方发布、且内容没被改",不证明"这个镜像没有漏洞、行为完全安全"。一个镜像可以被正确地签名,却仍含有已知漏洞或不该有的能力——签名是信任与完整性的地基,不是安全审查的终点,还要配合漏洞扫描(Trivy/Snyk)、最小权限、不可变 tag 等。其次,签名的可信最终取决于你的信任根(信任谁、怎么枚举),别随便信任一切"自称某邮箱签的"镜像。

把镜像签名放回自托管整体:它是在"拉镜像→跑容器"这条链上补上"可验证、可追溯"的一环。落地要点:构建后自动用 Sigstore/cosign 签名(keyless 短期证书 + Rekor),部署前校验且锁 digest、不通过即拒起;把签名嵌进 CI、把强制校验接进部署(Kyverno/policy);防住"签 tag 不锁 digest、只签自己层、密钥/日志管理、校验回放"四坑;并认清它保"来源与完整性"而非"无漏洞"。当你让跑起来的每个镜像都能验证"到底是谁、有没有被改",自托管才会从"拉来就用"走向"可信可审计"。

结语

镜像签名与供应链安全的价值,是给"拉镜像→跑容器"这条软链补上可验证、可追溯的一环,回答"我跑的就是发布方认可的那个镜像吗"。落地要点:用 Sigstore/cosign 在构建后自动签名(keyless + 短期证书 + Rekor),部署前用 cosign verify 校验并锁 digest、不匹配就拒绝启动;把签名嵌进 CI、把强制校验接进平台(Kyverno);防住签 tag 不锁 digest、只签自己层不审依赖、密钥与透明日志管理、校验回放四坑;并认清它证明"来源与完整性"而不等于"无漏洞",仍要配漏洞扫描与最小权限。当镜像从"靠名字信任"升级为"靠密码学验证",自托管的供应链才算真正立住了地基。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

负载均衡算法深度实战:在 Nginx/HAProxy 上做好加权、最少连接与会话保持

RabbitMQ 消息队列实战:把直连耦合改造成解耦削峰的异步流动

评 论
更换验证码