容器镜像标签是可变指针,摘要则标识内容。有效签名把一份关于该摘要的声明绑定到签名密钥;这把密钥是否代表获授权的人或工作负载,要由信任策略判断。单靠签名,不能证明谁构建了镜像、构建是否遵循所宣称的流程,也不能证明软件内容安全。这些问题需要不同证据,不能因为校验成功就合并成一个结论。

真正有用的边界,是部署之前的验证决定。校验方需要检查内容绑定和预期信任根,再执行身份与来源策略。registry 地址、熟悉的标签,或者存在某份签名,都不足以独立构成放行依据。必须知道自己信任谁、允许其声明什么,以及这份声明究竟覆盖哪一个产物。

摘要是身份,标签是指针

OCI 清单摘要对精确清单字节进行哈希,清单中的描述符再通过各自摘要标识配置与层,客户端也须核对这些绑定。在哈希抗碰撞假设下,清单改变会得到不同摘要。如果只修改存储层中的字节却不更新描述符,应触发内容完整性失败,而不是悄悄把原镜像重新定义成另一份内容。内容寻址依赖实际校验,不只是路径看起来像摘要。

Cosign 的普通镜像签名载荷包含镜像摘要,验证时会把它与请求的镜像比较。不同标签可以指向同一个已签名摘要;标签一旦移动,先前的校验不能覆盖新对象。应解析并验证目标摘要,再部署同一个摘要。如果校验之后重新按标签拉取,就留下检查与使用之间的竞争窗口;重复查询标签也不能彻底关闭这个窗口。

标签字符串不能绑定镜像内容

对字面字符串 app:v2 签名,认证的是这个声明,并不是名字后面不断变化的对象。签名也可以包含其他上下文,所以需要检查真实载荷格式与校验器行为,而不是把所有方案都说成只签一个裸哈希。不要为了让不匹配的载荷通过,就关闭摘要声明校验,否则签名有效与目标镜像之间的关键联系会丢失。

基于密钥的校验,需要由策略说明为什么信任所给公钥。无密钥校验还要检查预期证书身份与 OIDC 颁发者,以及证书链和所要求的透明日志证据。Fulcio 证书把身份关联到密钥,不会检查构建过程或认证软件质量。要判断构建声明,还需要适当的来源证明、受信任的构建者,以及证明中主体摘要与待运行产物一致的检查。

校验才是控制,签名不是

强制检查可以放在准入或部署前,但效果取决于实际覆盖范围。需要确认哪些工作负载与 registry 被纳入策略,以及校验失败时如何处理。只记录摘要不能证明发布获授权;即使签名正确、身份也获授权,仍不能据此排除所有漏洞或恶意行为。安全测试与构建来源验证应各自提供证据,不能相互替代。

密码学签名有效与当前策略接受,是两回事。退役密钥需要更新所有相关验证方的信任策略,仅生成一把新密钥不会自动完成撤销。短期证书配合可信签名时间证据,可以支持证书到期之后验证历史签名。透明日志让活动可检查,却不会自动撤销泄露身份或阻挡恶意发布。事故响应仍须决定保留哪些身份、时间窗口、摘要和证明,不能把日志存在等同于风险已解决。

坏掉的样子

部署步骤先验证一个摘要,随后又按可变标签拉取;另一次推送就可能让最终运行的字节变掉。需要把经过验证的摘要一直传到部署交接处,并明确它指向单平台清单还是多平台索引,确认运行时怎样选择具体平台镜像。这样才能说明实际运行对象与被检查对象之间的关系。

另一种失败是接受任何能通过密码学检查的签名者。应把信任限制到获授权密钥,或明确的身份与颁发者组合,并测试错误签名者和未批准摘要会被拒绝。发现泄露后需要调查并改变策略;这并不自动证明该密钥以前签过的所有产物都恶意,也不能承诺继续接受它们一定安全。

摘要绑定与签名者授权先于部署同一摘要;构建来源、内容安全、信任更新及对象发现需分别检查。
镜像签名信任:机制与证据边界。

快速验证

下面是命令模板,不是可直接运行的夹具:registry 是示例,摘要也被省略。进行获授权的 registry 检查之前,需提供已有测试镜像的完整摘要、可信公钥,并确认所安装的 Cosign 与 Skopeo 版本。本次源码审阅没有签名、操作 registry 或执行部署,因此没有新的集成运行结果。

cosign verify --key cosign.pub registry.example.com/app@sha256:9ab5a134...
cosign tree registry.example.com/app@sha256:9ab5a134...
skopeo inspect --format '{{.Digest}}' docker://registry.example.com/app:latest

预期结果是指定摘要在选定密钥下通过校验,不一定打印可读的人类身份。cosign tree 发现关联对象,列出对象不等于验证其中声明。Skopeo 可以查看标签当下指向;如果不同,只说明它与刚才检查的摘要不同,另一个摘要仍可能有自己的签名。即使两者相同,也只是观察时刻的快照,无法阻止标签随后移动。

来源