4. 镜像验证基础:使用 Cosign 验证镜像签名、理解签名载荷与公钥的关系
在 Serverless 流水线中,镜像签名验证是确保供应链安全的核心环节。Cosign 作为 Sigstore 项目下的签名工具,提供了开箱即用的镜像签名与验证能力。本章将深入讲解如何使用 Cosign 验证镜像签名,并剖析签名载荷的结构及其与公钥的绑定关系。
4.1 Cosign 验证命令与基本流程
Cosign 的验证命令 cosign verify 是流水线中最常用的操作。其基本语法如下:
cosign verify --key <公钥路径> <镜像引用>
验证流程包含三个关键步骤:
- 拉取签名:Cosign 根据镜像摘要(digest)从镜像仓库的标签或 OCI 构件中获取对应的签名对象。
- 公钥解密:使用指定的公钥对签名进行解密,得到原始签名载荷的哈希值。
- 哈希比对:计算当前镜像内容的哈希值,与解密后的哈希值进行比对,一致则验证通过。
实践要点:在 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 中或通过密钥管理服务分发。
安全风险:如果攻击者替换了流水线中使用的公钥,即使镜像被篡改,验证也可能通过。因此,公钥的完整性保护与镜像签名验证同等重要。
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 规则负责解析并做出决策。