4. 镜像签名实战:对容器镜像进行签名(cosign sign)

镜像签名是供应链信任的核心执行动作。通过 cosign sign 命令,开发者使用私钥对镜像摘要(Digest)进行数字签名,并将签名产物存储在 OCI 仓库中。本节将深入签名命令的完整用法、密钥指定方式、签名格式与元数据,以及签名后镜像的不可变性原理。

4.1 签名命令基础与密钥指定

cosign sign 的基本语法如下:

cosign sign --key <私钥路径> <镜像引用>

镜像引用支持 Tag 和 Digest 两种形式。推荐使用 Digest 形式以确保签名对象的确定性。

参数 说明 示例
--key 指定私钥文件路径或 KMS 资源标识符 --key cosign.key
--tlog-upload 是否将签名记录上传至 Rekor 透明度日志(默认 true) --tlog-upload=false
--annotations 附加自定义元数据到签名载荷 --annotations env=prod
--recursive 对多架构镜像的所有子镜像签名 --recursive
最佳实践: 在 CI/CD 流水线中,始终使用 --key env://COSIGN_PRIVATE_KEY 从环境变量读取密钥,避免密钥文件暴露在构建日志中。

4.2 签名格式与元数据

Cosign 生成的签名遵循 OCI 签名规范,签名产物以 application/vnd.dev.cosign.simplesigning.v1+json 的媒体类型存储在镜像仓库中。签名载荷包含以下关键字段:

  • critical:包含镜像标识(identity)、类型(type)和哈希(image.docker-reference/digest)。
  • optional:可扩展的元数据区域,支持自定义 annotations。
  • signature:使用私钥对上述 JSON 载荷进行签名的 Base64 编码值。

签名后的镜像在仓库中会多出一个 .sig 标签的镜像层,例如:

registry.example.com/myapp:latest
registry.example.com/myapp:sha256-xxxx.sig   # 签名层
元数据注入示例: 在签名时添加构建流水线 ID 和审批人信息,便于事后审计。
cosign sign --key cosign.key \
  --annotations "pipeline-id=build-1234" \
  --annotations "approver=alice@corp.com" \
  registry.example.com/myapp@sha256:abc123

4.3 签名后镜像的不可变性

签名操作本身不修改原始镜像层,而是将签名作为独立对象附加到仓库中。但签名与镜像摘要之间建立了加密绑定:一旦镜像内容发生变化(即使一个字节),镜像摘要就会改变,原有签名立即失效。

这种设计实现了逻辑不可变性:

  • 内容不变:签名不修改镜像层数据。
  • 绑定不变:签名载荷中记录了镜像的精确摘要。
  • 验证不变:任何对镜像的篡改都会导致摘要不匹配,验证失败。
签名后镜像不可变性原理 原始镜像层 sha256:abc123 cosign sign 私钥签名 签名层 .sig 绑定摘要 abc123 篡改后镜像层 sha256:xyz789 cosign verify 摘要不匹配 签名层 .sig 仍绑定 abc123 核心结论:签名不修改镜像内容,但通过摘要绑定实现篡改检测 镜像内容变化 → 摘要变化 → 签名验证失败 → 信任链断裂

4.4 多架构镜像签名

对于支持多架构(如 amd64、arm64)的镜像,Cosign 提供 --recursive 参数实现批量签名:

cosign sign --key cosign.key --recursive \
  registry.example.com/myapp:latest

该命令会:

  1. 解析 manifest list,获取所有子镜像的 digest。
  2. 对每个子镜像分别生成签名。
  3. 为 manifest list 本身也生成一个顶层签名。
注意: 多架构签名时,必须确保所有子镜像的 digest 在签名前已固定。不要在签名过程中同时推送新镜像,否则会导致签名与镜像不一致。

4.5 签名验证与信任边界

签名完成后,使用 cosign verify 验证签名有效性:

cosign verify --key cosign.pub \
  registry.example.com/myapp@sha256:abc123

验证通过的条件:

  • 签名载荷中的 digest 与当前镜像 digest 一致。
  • 签名使用对应的公钥解密成功。
  • 签名未过期(如果签名时设置了有效期)。

下表总结了签名前后镜像状态的变化:

维度 签名前 签名后
镜像层内容 原始层 不变
仓库中对象 镜像层 + 配置 + manifest 增加 .sig 签名层
可篡改性 可被中间人替换 篡改后验证失败
信任锚点 无 公钥 / KMS 密钥
生产环境建议: 在 CI 流水线中,将签名步骤放在镜像推送之后、部署之前。使用 cosign copy 将签名同步到灾备仓库,确保签名与镜像始终共存。