容器供应链攻击正成为云原生时代最隐蔽的安全风险:攻击者只需篡改镜像仓库或投毒基础镜像,就能让恶意代码随部署流入生产。本文用 Cosign(Sigstore 生态核心工具)给容器镜像签名,从本地构建、CI 自动签名到集群准入校验,全程保证镜像来源可信、未被篡改,构筑一道抵御供应链攻击的防线。
一、为什么需要镜像签名
传统部署流程默认“拉到什么就跑什么”。但镜像在构建、推送、拉取链路上有太多被动手脚的机会:基础镜像被投毒、私有仓库遭中间人替换、同一 tag 背后镜像内容悄悄变更。一旦恶意层进入生产,凭据泄露、后门植入往往难以追溯。
一条典型的投毒链路是:攻击者往热门基础镜像塞入后门 → 你的 CI 基于它构建出业务镜像 → 镜像被推送进共享仓库 → 集群拉取运行。由于全程没有来源校验,恶意层会一路畅通。镜像签名正是要在“构建完成”与“运行之前”这两个关口插入可信验证,让任何非可信来源的镜像在调度前就被拦下。
镜像签名要解决的正是“信任”问题:发布方用私钥对镜像 digest 签名,消费方在运行前用公钥验证——只有签名通过且来自可信身份,才允许部署。这把安全左移到交付环节,而不是等运行时告警。
二、Cosign 是什么
Cosign 是 Sigstore 项目的签名引擎,负责对 OCI 制品(镜像、blob、SBOM)进行签名与验证。它有两种模式:
- Keyed 模式:用你自管的非对称密钥对(推荐小团队起步)。
- Keyless 模式:无需长期私钥,借助 Fulcio(短期证书 CA)、Rekor(透明日志)与你的 OIDC 身份(GitHub/Google)完成短时签名,私钥用完即销毁。
Sigstore 不是单点工具,而是一套零信任签名基础设施:cosign 负责签名动作,Fulcio 是无状态短期 CA(用 OIDC 身份签发代码签名证书),Rekor 是防篡改的透明日志(记录每次签名证据,任何人可审计),三者共同消弭了“私钥长期持有”这一最大风险点。
三、安装与本地快速上手
先装好 Cosign CLI(macOS 用 Homebrew,Linux 直接拉二进制):
# macOS
brew install cosign
# Linux
curl -sSfL https://raw.githubusercontent.com/sigstore/cosign/main/release/release.sh \
| sh -s -- v2.4.0
cosign version
Keyed 模式三步走:生成密钥对 → 对镜像签名 → 验证。注意签名对象是镜像 digest 而非可变的 tag。
# 1. 生成密钥对(会提示设置私钥口令)
cosign generate-key-pair
# 2. 对镜像签名(强烈建议锁定 digest)
cosign sign --key cosign.key \
registry.example.com/app@sha256:abc123...
# 3. 验证(消费方只需 cosign.pub)
cosign verify --key cosign.pub \
registry.example.com/app@sha256:abc123...
四、无密钥签名:Keyless 模式
不想长期保管私钥?Keyless 模式用你的 GitHub/Google 身份换取 Fulcio 签发的短期证书,并将签名写入 Rekor 公开透明日志,任何人都能审计。首次使用会弹出 OIDC 登录。
# Keyless 签名(自动走 OIDC 登录)
cosign sign registry.example.com/app@sha256:abc123...
# 验证时收紧身份,防止伪造签发者
cosign verify \
--certificate-identity-regexp "https://github.com/your-org/.*" \
--certificate-oidc-issuer "https://oauth2.sigstore.dev/auth" \
registry.example.com/app@sha256:abc123...
除了镜像本身,cosign 还能对 SBOM、provenance(构建来源)等制品签名。例如用 cosign attest 把 SLSA 来源证明附加到镜像,让消费方不仅能验证“是谁签的”,还能验证“是怎么构建的”,进一步压缩伪造空间。
五、在 CI 中自动签名
手动签名容易遗漏,正确做法是在流水线里自动签名。下面是 GitHub Actions 片段,借助官方 cosign-action 在推送镜像后用 Keyless 模式签名并写入 Rekor。完整 CI/CD 编排思路可参考我们的《GitHub Actions 实战:自动化构建与部署流水线》(p=265)。
name: build-and-sign
on: [push]
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write # 关键:授予 OIDC 令牌供 Keyless 签名
contents: read
steps:
- uses: docker/build-push-action@v6
with:
push: true
tags: registry.example.com/app:${"{ github.sha }"}
- uses: sigstore/cosign-installer@v3
- name: Sign image
run: |
cosign sign \
registry.example.com/app@${"{ github.sha }"}
六、在集群侧强制校验
签名本身不等于安全,必须有人“卡口”。在 Kubernetes 侧,最省心的方案是用 Kyverno(策略即代码)配置镜像验签策略,未通过签名的镜像直接拒绝调度。下面是一条最小策略骨架:
# Kyverno ClusterPolicy(示意)
verifyImages:
- image: "registry.example.com/app"
key: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD...
-----END PUBLIC KEY-----
# 或用 keyless 校验身份
attestors:
- entries:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://oauth2.sigstore.dev/auth
subjectRegex: https://github.com/your-org/.*
七、与准入控制 / OPA 协同
若你的集群已用 OPA(Open Policy Agent)统一策略决策,也可以把镜像签名校验下沉到准入层,与鉴权、配额策略共用一套决策引擎,避免策略碎片化。无论用 Kyverno 还是 OPA,核心都是“默认拒绝 + 显式放行可信签名方”。
八、Keyed 与 Keyless 怎么选
| 维度 | Keyed(自管密钥) | Keyless(Sigstore) |
|---|---|---|
| 私钥管理 | 需妥善保管 cosign.key | 无需长期私钥,用完即弃 |
| 身份来源 | 你自己分发公钥 | OIDC(GitHub/Google 等) |
| 可审计性 | 依赖你自建流程 | Rekor 公开透明日志 |
| 适用规模 | 小团队 / 离线环境 | 中大型 / 多协作方 |
九、签名之外:SBOM 与漏洞扫描形成纵深
签名解决“来源可信”,但不解决“可信来源里有没有已知漏洞”。因此签名必须和 SBOM 生成、镜像漏洞扫描叠加,构成纵深防御。我们可以在 CI 里用 Trivy 先扫 CVE,再让 Cosign 签名,最后由准入策略同时校验“已签名 + 无高危漏洞”。三类手段各管一段:
| 手段 | 解决的问题 | 作用阶段 |
|---|---|---|
| 镜像签名(Cosign) | 来源可信、未被篡改 | 构建后 / 运行前 |
| SBOM 生成 | 看清依赖清单与许可证 | 构建时 |
| 漏洞扫描(Trivy) | 已知 CVE 风险 | 构建后 / 准入前 |
十、常见误区与排查
- 只在本地签名不验证:签名没有消费方校验形同虚设,务必在准入层强制。
- 对 tag 签名:tag 可变,攻击者能替换内容保留签名,应锁定
@sha256:digest。 - 公钥随意分发:公钥应通过可信渠道(配置管理 / Secret)注入,而非硬编码在公开仓库。
- Keyless 忘开 OIDC 权限:CI 需授予
id-token: write,否则拿不到短期证书。 - 密钥从不轮转:Keyed 模式下定期轮转 cosign 密钥,并保留旧公钥一段时间用于过渡验证,旧密钥泄露时可快速吊销。
十一、落地建议
从小处起步:先按《Docker 实战:容器化部署从零到生产》规范镜像构建,再用 Keyless + GitHub Actions 把签名嵌入流水线,最后用 Kyverno 在集群侧强制校验。三层叠加,容器供应链攻击面会被大幅压缩。签名不是银弹,但它是云原生安全里投入产出比极高的一道必选项。




