4. 镜像签名基础:使用 Cosign 对容器镜像进行签名、签名格式解析、签名存储位置

容器镜像签名是供应链安全的第一道防线。Cosign 作为 Sigstore 生态的核心工具,提供了开箱即用的镜像签名与验证能力。本节将深入讲解如何使用 Cosign 对镜像进行签名、解析签名数据的内部结构,以及签名在镜像仓库中的实际存储机制。

4.1 使用 Cosign 对容器镜像进行签名

Cosign 支持两种主要的签名模式:密钥对签名(适用于企业自有 PKI)和 Keyless 签名(基于 OIDC 身份与 Fulcio 证书)。以下分别演示两种方式。

4.1.1 密钥对签名

首先生成密钥对,然后对镜像进行签名:

# 生成密钥对(私钥加密存储)
cosign generate-key-pair

# 输出文件:
#   cosign.key  — 私钥(需妥善保管)
#   cosign.pub  — 公钥(分发给验证方)

# 对镜像签名(需先推送镜像到仓库)
cosign sign --key cosign.key registry.example.com/myapp:v1.0.0

# 验证签名
cosign verify --key cosign.pub registry.example.com/myapp:v1.0.0

4.1.2 Keyless 签名(无密钥模式)

Keyless 模式利用 GitHub/GitLab/Google 等 OIDC 身份自动获取短期证书,无需管理密钥:

# 直接签名(自动触发 OIDC 流程)
cosign sign registry.example.com/myapp:v1.0.0

# 验证(自动拉取证书链与 Rekor 时间戳)
cosign verify registry.example.com/myapp:v1.0.0
生产建议: 在 CI/CD 流水线中优先使用 Keyless 签名,避免私钥泄露风险;对于离线环境或严格合规场景,使用密钥对签名并配合硬件安全模块(HSM)保护私钥。

4.2 签名格式解析

Cosign 生成的签名并非单一文件,而是由多个组件构成的 签名包(Signature Bundle)。下表列出了签名包的核心组成部分:

组件 格式 说明
签名载荷 Base64 编码的 JSON 包含镜像 Digest、签名算法、签名时间等元数据
签名值 Base64 编码的原始签名 对载荷的签名结果(ECDSA P-256 或 RSA)
证书链 PEM 格式 Keyless 模式下包含 Fulcio 签发的短期证书及中间 CA
Rekor 条目 JSON 透明日志中的条目 ID,用于审计与不可否认性

以下是一个签名载荷的示例(解码后):

{
  "critical": {
    "identity": {
      "docker-reference": "registry.example.com/myapp"
    },
    "image": {
      "docker-manifest-digest": "sha256:abc123..."
    },
    "type": "cosign container image signature"
  },
  "optional": {
    "creator": "cosign 2.2.0",
    "timestamp": "2025-03-15T10:30:00Z"
  }
}
关键字段解析:
  • critical.image.docker-manifest-digest — 签名绑定的镜像摘要,防止签名被挪用到其他镜像。
  • critical.identity.docker-reference — 签名时使用的镜像引用,用于验证上下文。
  • optional.timestamp — 签名时间戳(Keyless 模式下由 Rekor 提供不可篡改的时间证明)。

4.3 签名存储位置:镜像仓库注解

Cosign 将签名存储在 镜像仓库的注解(Annotations) 中,而非修改镜像层本身。具体存储机制如下:

  1. 签名作为独立 OCI 制品:Cosign 将签名包打包为一个 OCI 镜像制品,推送到与原始镜像相同的仓库。
  2. 命名约定:签名制品的标签为 sha256-<镜像摘要>.sig,例如 sha256-abc123....sig。
  3. 注解键值对:签名元数据存储在制品的注解中,键为 dev.cosignproject.cosign/signature,值为 Base64 编码的签名载荷。

以下 SVG 展示了签名在镜像仓库中的存储结构:

Cosign 签名在镜像仓库中的存储结构 镜像仓库 registry.example.com 原始镜像 myapp:v1.0.0 签名制品 sha256-abc123....sig 签名制品注解(Annotations) 键:dev.cosignproject.cosign/signature 值:Base64 编码的签名载荷(包含镜像 Digest、签名值、证书链) 附加注解:dev.cosignproject.cosign/cert(PEM 证书,Keyless 模式) 附加注解:dev.cosignproject.cosign/chain(中间 CA 证书链)
重要注意事项:
  • 签名制品与原始镜像共享同一个仓库,但作为独立的 OCI 清单存在,不会污染镜像层。
  • 删除原始镜像时,签名制品不会自动删除,需要单独清理(建议使用 cosign clean 命令)。
  • 部分镜像仓库(如 Harbor、Docker Hub)支持在 UI 中查看签名注解,但大多数仓库需要 API 查询。

4.4 签名验证流程

验证签名时,Cosign 执行以下步骤:

  1. 拉取签名制品:根据镜像 Digest 构造签名制品标签 sha256-<digest>.sig。
  2. 解析注解:从签名制品中读取 dev.cosignproject.cosign/signature 注解。
  3. 解码签名载荷:Base64 解码后得到 JSON 载荷,提取镜像 Digest 和签名值。
  4. 验证签名:使用公钥或 Fulcio 证书验证签名值是否与载荷匹配。
  5. 验证镜像 Digest:确认载荷中的 Digest 与当前镜像的 Digest 一致,防止签名复用攻击。
  6. (可选)查询 Rekor:验证签名是否已记录在透明日志中,确保签名的时效性与不可否认性。

以下 SVG 展示了完整的签名验证流程:

Cosign 签名验证流程 1. 拉取签名制品 2. 解析注解 3. 解码载荷 4. 验证签名值 5. 验证镜像 Digest 6. 查询 Rekor(可选) 验证通过 ✓
验证命令示例:
# 使用公钥验证
cosign verify --key cosign.pub registry.example.com/myapp:v1.0.0

# Keyless 验证(自动拉取证书与 Rekor)
cosign verify registry.example.com/myapp:v1.0.0

# 输出示例(验证通过时):
# Verification for registry.example.com/myapp:v1.0.0 --
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - Existence of the claims in the transparency log was verified
#   - The signatures were verified against the specified public key

4.5 签名管理最佳实践

  • 签名策略:在 Kubernetes 集群中配置 cosign verify-admission 准入控制器,强制所有部署的镜像必须经过签名验证。
  • 密钥轮换:使用密钥对签名时,定期轮换密钥并更新公钥分发机制(如存储在 Kubernetes Secret 或 OCI 仓库中)。
  • 签名审计:利用 Rekor 透明日志审计所有签名操作,确保签名行为的可追溯性。
  • 多签名支持:Cosign 支持对同一镜像添加多个签名(不同密钥或不同身份),适用于多团队协作场景。