没有证据就不发布
一句话总结
清单、签名和证明,在发布前如果没有被读取,就等于不存在,而关卡的价值 不在于拦住了什么,而在于人能不能读懂它为什么拦住。
为什么需要它
前面三个模块中做出来的全部是证据。写明里面有什么的清单、说明这些字节 属于我们的签名、记录来自哪里的证明。然而,证据如果不提交到法庭上, 就什么作用也起不了。在发布前打开它们查看,不足就停下的流程,就是关卡。
建立关卡时,人们最常犯的错误是只看签名。签名验证只说明 “这些字节是用这把密钥签名的”。那把密钥是不是我们的,带有那个签名的证明 是不是真的出自我们的构建者,证明所指向的对象是不是现在要发布的那个文件, 这些都是需要各自单独询问的问题。三者缺了任何一个,都会出现通过的路径。
工作原理
关卡提出的问题归纳为四个。
목록이 붙어 있는가 SBOM 이 있고 비어 있지 않은가
서명이 맞는가 우리가 믿는 키로 검증되는가
대상이 같은가 증명의 subject 다이제스트가 지금 이 파일의 해시와 같은가
빌더를 허락했는가 builder.id 가 허용 목록에 있는가
该代码块中的韩文依次说明:清单是否附带,即是否存在 SBOM 且不为空;签名是否正确,即能否用我们信任的密钥验证通过;对象是否相同,即证明中 subject 的摘要是否与当前这个文件的哈希一致;是否允许该构建者,即 builder.id 是否在白名单中。
第四行经常被漏掉,而在实践中最悄无声息地被突破的地方就在这里。签名正确,
摘要也正确,但那次构建来自某人的笔记本电脑,只看前三项的关卡
会原样放行。SLSA 之所以在 Build L2 要求专用基础设施之上的托管平台,
原因就在这里——在哪里构建,本身就是一项安全属性
(SLSA 安全等级)。cosign 的验证也
朝着同一个方向。文档写明 cosign verify 不仅验证签名,还会确认签名证书
是否来自受信任的证书颁发机构
(cosign 验证)。
另一个重要的设计选择是把原因留成代码。如果失败消息只用句子表达,
每个人写出来都不一样,这样就无法统计“上个月最常因为什么被拦住”。
如果用 signature_missing 或 builder_not_allowed 这样简短的代码留存,判定
本身就成了统计数据,有了统计数据,接下来该修复什么就不是靠争论,而是由数字
来决定。
最后,必须在关卡之内预留豁免的位置。没有例外的关卡,在紧急时会 被整个关掉,而一旦关掉,就不会再打开。如果让例外留下记录, 关卡就能保持开启。
在现场相遇的样子
最常见的失败是关卡只发出警告却不停下。流水线日志里留下一行黄色 警告,发布照常进行。起初的理由是“等习惯了再说”, 后来就没有人读那一行了。不会停下的关卡就不是关卡。
第二种是不先确认通过的情况。如果先从拦截开始测试,“总是 拦截”的关卡也会看起来像是成功了。先固定正常发布能够通过,再 逐个制造应当被拦住的情况,这样双向才都得到确认。
第三种是不留下判定。关卡拦住了这件事,只在那一刻存在于屏幕上, 几周后问起“当时为什么被拦住”,没有人能回答。如果把判定、依据和对象的 摘要写成一行留存下来,这个问题就变成了可以回答的问题。
第四种是关卡设置得太晚。如果只在发布的瞬间检查,那时发布已经 排定,人们也已经聚在一起,一被拦住,就会有人提议把关卡关掉。如果在 构建之后也运行一次同样的检查,问题会提前几个小时暴露,而发布前的检查 就更接近于确认。只是在两处调用同一个程序,所以几乎不增加成本。
第五种是关卡自己制造它所查看的证据。如果在发布前重新生成 SBOM 再检查,那份清单就不是构建实际使用的,而是现在重新解析出来的。两者出现 偏差的那一天,正是想要确认的那一天,而这种偏差却看不见了。证据 应当由生成的一方附上,关卡只应该读取。
第六种是开启关卡的顺序。如果一开始就把四项检查全部强制运行,已有的 发布版本会全部被拦住,当天关卡就会被关掉。在实践中能存活的顺序是:先用很短的 一段时间只记录原因,从数字上看出哪些原因各有多少条之后,再逐个 改为强制。即便如此,也必须同时钉死“到什么时候全部改为强制”这个日期—— 没有日期,只记录的阶段就永远不会结束。
下一项实验要做什么
给发布包附上来源证明并签名,制作一个检查四项内容、并带着原因代码拦截的关卡。 先确认正常的发布包能够通过,然后亲手制造三种发布包:没有签名的发布包、制品被改动的 发布包、签名正确但由未获许可的构建者生成的发布包,确认它们分别因不同的原因 被拦住,并把这些判定留作审计记录。