没人知道这段字节是从哪来的
一句话总结
来源证明(provenance)是在构建时记下“什么东西、由什么、由谁、在什么时候创建的”, 而 SLSA 则把这份记录有多可信划分成了若干等级。
为什么需要它
即使清单和签名都有了,仍然剩下一个回答不了的问题:“这些字节是从哪里来的。” 签名回答“是谁签的名”,SBOM 回答“里面有什么”, 但两者都没有说明这个制品是从哪份源码、经过什么流程产生的。
这个问题真正成为麻烦的场景并不戏剧化。通常是这样——因为着急, 有人在自己的笔记本电脑上构建并上传了,这个制品上也带有公司密钥的签名。 签名通过了,清单也有。但是这次构建来自哪个提交、 用了哪些依赖,没有人知道。几个月后出了问题, 一条可以回溯的线索都没有。
工作原理
SLSA 把这件事划分为轨道和等级。在 Build 轨道中,L0 是没有任何保证的 状态,L1 是存在一份记录制品是如何生成的来源证明。 L1 的证明可能不完整,也可能没有签名,所以能防止失误,却防不住伪造。 L2 是由专用基础设施之上的托管构建平台生成并签署证明,因此 能防止构建之后的篡改。L3 会加固构建平台本身,使各次运行之间 无法相互影响,并让自定义构建步骤无法接触用于证明签名的机密 (SLSA 安全等级)。这份等级文档目前把 v1.0 版本 标记为 “Retired”,并指向更新的版本——引用时需要核对这一点。
证明的格式使用 in-toto Statement。最上面是 _type 和 subject,
并用 predicateType 指明谓词的类型。SLSA 文档在这里明确规定,要原样填入
https://slsa.dev/provenance/v1,而不是地址栏中显示的地址。
谓词内部分为两部分。
buildDefinition 무엇을 만들라고 했는가
buildType 이 칸들을 어떻게 읽어야 하는지 가리키는 URI
externalParameters 빌드에 밖에서 넣은 값 (소스 주소와 커밋, 진입점 등)
internalParameters 플랫폼이 스스로 채운 값
resolvedDependencies 실제로 쓴 재료와 그 다이제스트
runDetails 누가 언제 실행했는가
builder.id 이 빌드를 수행한 플랫폼의 신원
metadata invocationId · startedOn · finishedOn
该代码块中的韩文依次说明:buildDefinition 表示要求构建什么,其中 buildType 是指明如何解读这些字段的 URI,externalParameters 是从外部传入构建的值(源码地址与提交、入口点等),internalParameters 是平台自己填写的值,resolvedDependencies 是实际使用的材料及其摘要;runDetails 表示谁在何时执行,其中 builder.id 是执行该构建的平台的身份,metadata 包含 invocationId、startedOn、finishedOn。
文档把 Build L1 的必填项定为 buildDefinition 和 runDetails,以及其中的
buildType 和 externalParameters,还有 builder
(SLSA Provenance v1)。其余的是有则更好的
字段。尤其是 builder.id,文档专门说明它是承载整个信任边界的字段——
因为它是在声明信任该标识符所指向的平台。
resolvedDependencies 的价值在于为每项材料同时记下摘要。
如果只写名称,就无法追问“用同样的输入重新构建,会得到同样的东西吗”。
有了摘要,这个问题在以后、甚至在出事故之后也可以提出。
在现场相遇的样子
第一个陷阱是生成了证明,却没有人读。流水线多输出一份 JSON, 把它上传到制品库就结束了。如果在发布前没有打开这个文件查看的流程, 这份证明仅仅存在,并不会改变任何事情。
第二个是把整个构建环境一股脑塞进 externalParameters。文档建议把这个字段
保持在最小。值越多,验证方就越难定义“什么是正常的”,
最终变成一大堆没有人去比较的东西。
第三个是写分支名而不是提交哈希。分支是移动的指针, 同一份证明在不同时间会指向不同的源码。必须用摘要固定下来。
第四个是搞混了由谁生成证明。证明只有由执行构建的一方生成, 才有价值。构建结束后由人手工填写的证明,只会写进那个人知道的内容, 而那个人一旦被骗,证明也会随之被骗。这就是 SLSA 随着等级提高, 不断收窄“由谁生成证明”的原因——L2 由托管平台生成并签署, L3 则让用户的构建步骤无法接触那个签名机密。
下一项测验要确认什么
确认 Build 轨道的每个等级能防止什么,证明的哪些字段是 Build L1 的必填项,
以及 builder.id 为什么是承载信任边界的字段。在后续模块中,
将真正生成并签署这份证明,并建立读取它的关卡。