4. 镜像验证基础:使用 Cosign 验证镜像签名、理解签名载荷与公钥的关系

在 Serverless 流水线中,镜像签名验证是确保供应链安全的核心环节。Cosign 作为 Sigstore 项目下的签名工具,提供了开箱即用的镜像签名与验证能力。本章将深入讲解如何使用 Cosign 验证镜像签名,并剖析签名载荷的结构及其与公钥的绑定关系。

4.1 Cosign 验证命令与基本流程

Cosign 的验证命令 cosign verify 是流水线中最常用的操作。其基本语法如下:

cosign verify --key <公钥路径> <镜像引用>

验证流程包含三个关键步骤:

  1. 拉取签名:Cosign 根据镜像摘要(digest)从镜像仓库的标签或 OCI 构件中获取对应的签名对象。
  2. 公钥解密:使用指定的公钥对签名进行解密,得到原始签名载荷的哈希值。
  3. 哈希比对:计算当前镜像内容的哈希值,与解密后的哈希值进行比对,一致则验证通过。
实践要点:在 Serverless 流水线中,推荐使用镜像摘要(image@sha256:...)而非标签(image:latest)进行验证,避免标签被篡改导致的验证绕过风险。

4.2 签名载荷结构解析

Cosign 生成的签名并非简单的二进制数据,而是遵循特定格式的 JSON 载荷。理解载荷结构是判断签名有效性的基础。

字段 类型 说明
critical 对象 包含验证必需的核心信息,如镜像标识、身份等
critical.identity 对象 签名者身份声明,包含 docker-reference 和 issuer
critical.image 对象 镜像摘要信息,包含 docker-manifest-digest
critical.type 字符串 签名类型,固定为 cosign container image signature
optional 对象 可选元数据,如 OIDC 令牌、时间戳、自定义声明等

一个典型的签名载荷示例如下:

{
  "critical": {
    "identity": {
      "docker-reference": "registry.example.com/my-app"
    },
    "image": {
      "docker-manifest-digest": "sha256:abc123..."
    },
    "type": "cosign container image signature"
  },
  "optional": {
    "issuer": "https://.actions.githubusercontent.com",
    "subject": "repo:my-org/my-repo:ref:refs/heads/main"
  }
}
关键理解:签名载荷中的 critical.image.docker-manifest-digest 字段直接绑定镜像内容。任何对镜像层的修改都会导致该哈希值变化,从而使签名失效。

4.3 公钥与签名的绑定关系

Cosign 采用非对称加密机制:私钥用于签名,公钥用于验证。签名载荷与公钥的绑定关系通过以下数学过程实现:

  • 签名生成:私钥对载荷的哈希值进行加密,生成签名数据。
  • 验证过程:公钥对签名数据解密,得到原始哈希值,再与镜像实际哈希比对。
  • 信任锚点:公钥本身需要被信任。在流水线中,通常将公钥存储在 OPA 策略的 data 中或通过密钥管理服务分发。
安全风险:如果攻击者替换了流水线中使用的公钥,即使镜像被篡改,验证也可能通过。因此,公钥的完整性保护与镜像签名验证同等重要。
Cosign 签名验证流程与载荷结构 Cosign 签名验证流程与载荷结构 验证流程 输入镜像引用 (digest) 从仓库拉取签名对象 公钥解密签名 → 载荷哈希 比对镜像哈希与载荷哈希 验证通过 / 失败 签名载荷结构 critical.type: "cosign container..." critical.image.docker-manifest-digest critical.identity.docker-reference optional.issuer (OIDC 签发者) optional.subject (身份主体) 载荷哈希与镜像哈希一致 签名才被认定为有效

4.4 公钥分发与信任模型

在 Serverless 流水线中,公钥的分发方式直接影响策略的可信度。常见的公钥管理模型包括:

模型 描述 适用场景
静态公钥文件 将公钥以文件形式存储在流水线环境变量或 OPA data 中 小型团队、固定流水线
密钥管理服务 通过 KMS(如 AWS KMS、GCP Cloud KMS)动态获取公钥 企业级、多环境流水线
Sigstore 公共信任根 使用 Sigstore 的 Fulcio 和 Rekor 进行无密钥签名与验证 开源项目、公共镜像
OPA 策略内嵌 将公钥哈希或公钥内容直接写入 Rego 策略规则 策略即代码、不可变基础设施
最佳实践:在 OPA 策略中验证签名时,建议同时检查公钥的指纹(fingerprint)或公钥的 OIDC 签发者,防止公钥被替换后仍能通过验证。

4.5 验证失败常见原因与排查

流水线中镜像签名验证失败通常由以下原因导致:

  • 公钥不匹配:验证时使用的公钥与签名时使用的私钥不配对。
  • 镜像摘要变更:镜像被重新构建或推送,摘要发生变化,旧签名失效。
  • 签名格式错误:签名对象损坏或不是 Cosign 标准格式。
  • OIDC 令牌过期:使用 Sigstore 无密钥签名时,OIDC 令牌过期导致验证失败。
排查命令:使用 cosign verify --key <公钥> --verbose <镜像> 可以输出详细的验证日志,帮助定位失败原因。
公钥与签名绑定关系 公钥与签名绑定关系 签名者(私钥) 镜像内容 签名数据 计算哈希 加密 验证者(公钥) 签名载荷 哈希比对 解密 提取哈希 公钥只能解密由对应私钥签名的数据,从而建立签名与公钥的强绑定关系

4.6 在 OPA 策略中集成 Cosign 验证

OPA 策略可以通过调用外部命令或解析 Cosign 验证输出来实现签名验证。典型的 Rego 规则片段如下:

package image_signature

# 定义允许的公钥指纹列表
allowed_public_keys := {
  "sha256:abc123...",
  "sha256:def456..."
}

# 验证镜像签名是否有效
deny[msg] {
  input.image_ref
  signature_result := data.cosign.verify(input.image_ref, input.public_key)
  not signature_result.valid
  msg := sprintf("镜像 %v 签名验证失败", [input.image_ref])
}

# 验证公钥是否在允许列表中
deny[msg] {
  input.public_key_fingerprint
  not input.public_key_fingerprint in allowed_public_keys
  msg := sprintf("公钥 %v 不在允许列表中", [input.public_key_fingerprint])
}
集成要点:在 Serverless 流水线中,OPA 策略通常作为准入控制器(Admission Controller)运行,在镜像部署前执行签名验证。Cosign 验证结果以 JSON 格式传递给 OPA,Rego 规则负责解析并做出决策。