把二十五个逐一数清并写成 SBOM
目标
从一个锁文件中亲手统计“里面有什么”,并写成 CycloneDX 格式的 SBOM, 同时追踪没有人挑选过的软件包是通过哪条路径进来的。
为什么重要
事故发生后最先来的问题是“我们用了那个吗”。如果一个组织无法在几个小时 内回答这个问题,即使答案是“没有”,也要耗费好几天。快速得出答案的 办法不是事后去找,而是每次发布时就一并生成清单。 然而清单中的大部分并不是我们挑选的——我们亲手写下的七个依赖 会拉来二十五个。这二十五个到底会被打进发布物,还是只在构建时使用, 也必须区分清楚。因为需要修复的清单和紧急的清单,是两份不同的清单。
步骤
- 从
/opt/fixtures/sbom/release/package.json中提取直接依赖,写入/root/sbom/01-direct.json。 - 统计
/opt/fixtures/sbom/release/package-lock.json中已安装的全部内容,写入/root/sbom/02-installed.json。 - 区分生产依赖和开发专用依赖,写入
/root/sbom/03-scope.json。 - 追踪
sock-ttl是通过哪条路径进来的,写入/root/sbom/04-why.json。 - 以 CycloneDX 1.6 格式生成
/root/sbom/05-bom.json。 - 把制品的摘要固定到
/root/sbom/06-subject.json中。 - 只保留会被打进发布物的内容,生成
/root/sbom/07-runtime-bom.json。 - 与上一次发布比较,生成
/root/sbom/08-diff.json。
参考
- 所有工作都在
/root/sbom下进行。请先执行mkdir -p /root/sbom。 - 材料在
/opt/fixtures/sbom中。每样东西为什么在那里,写在MATERIAL-CARD.md里。 - 软件包名称和公告编号都是为实验虚构的,并非真实软件包的情况。
- 这个镜像里没有 syft 或 grype,Pod 也只开放了 DNS。直接读取锁文件 就是本实验的方式,这样也能看出工具会跳过什么。
- 常见错误——把锁文件的根条目(空字符串键)也一起统计,导致数量多出一个;
以及把没有
dev标志的条目误当作开发专用。 - 不要随意编造数字。评分器会用同样的材料重新计算并比对。
先统计我们亲自挑选的依赖
读取 /opt/fixtures/sbom/release/package.json,保存到 /root/sbom/01-direct.json。runtime 是按字典序排列的 dependencies 名称数组,dev 是按字典序排列的 devDependencies 名称数组。
package.json 中写的只有“我们挑选的依赖”。上面带有版本范围(^),所以只需提取名称。已安装的全部内容并不在这里。
统计实际安装的内容
读取 /opt/fixtures/sbom/release/package-lock.json,把 lockfile_version、total、direct、transitive 保存到 /root/sbom/02-installed.json。total 是 packages 映射中去掉根条目(键为空字符串)之后的数量,direct 是第 1 步统计出的直接依赖数量,transitive 是其余部分。
lockfileVersion 3 的 packages 是以安装位置为键的映射,根项目以空字符串键放入其中。如果不去掉根再统计,就会多出一个。
区分会被打进发布物的内容和只在构建时使用的内容
把 runtime、dev、dev_names 保存到 /root/sbom/03-scope.json。锁文件条目中 dev 为 true 的就是开发专用。runtime 和 dev 是数量,dev_names 是按字典序排列的开发专用软件包名称数组。
npm 文档写明,只有在 'strictly part of the devDependencies tree' 时,dev 才为 true。完全没有这个标志的条目,意味着属于生产一侧。
追踪没有人挑选过的软件包是如何进来的
沿着锁文件中的 dependencies 追踪 sock-ttl 为什么被安装,把 package、version、direct、depth、paths 保存到 /root/sbom/04-why.json。paths 是由“从根开始的名称数组”组成的数组,第一个元素是 paygate。depth 是该路径的边数。
锁文件每个条目中的 dependencies 就是边。从根开始按广度优先向下遍历,看看那个名称最先出现在哪里。
用 CycloneDX 格式写下清单
把 SBOM 保存到 /root/sbom/05-bom.json。bomFormat 为 "CycloneDX",specVersion 为 "1.6",version 为 1,metadata.component 为 paygate 1.4.2(type 为 application,purl 为 pkg:npm/paygate@1.4.2),components 按名称字典序放入已安装的 25 个。每个条目包含 type "library"、name、version、purl(pkg:npm/名称@版本)和 scope,scope 在开发专用时为 "excluded",否则为 "required"。
bomFormat 的值被固定为 "CycloneDX",是因为 BOM 文件没有命名规则。因为必须仅凭文件本身就能知道这是什么格式。
固定这份清单属于哪个制品
计算 /opt/fixtures/sbom/release/paygate-1.4.2.js 的 SHA-256,把 name(只写文件名)、version、bytes、sha256 保存到 /root/sbom/06-subject.json。sha256 是小写十六进制的 64 个字符。
如果清单中没有写明对象,就没有人能证明“这是什么的清单”。请用 hashlib.sha256 对文件字节做哈希。
只保留会被打进发布制品的内容
从 /root/sbom/05-bom.json 中去掉 scope 为 excluded 的条目,保存到 /root/sbom/07-runtime-bom.json。格式与 05-bom.json 相同,只是 components 减少了。
开发专用依赖的漏洞不会影响到用户。如果把两者混在一起统计,需要修复的数字就会膨胀,真正紧急的反而被淹没。
统计与上一次发布相比有什么变化
把 /opt/fixtures/sbom/sbom/paygate-1.4.1.cdx.json 与 /root/sbom/05-bom.json 比较,把 added、removed、changed、unchanged 保存到 /root/sbom/08-diff.json。added 和 removed 是包含 name、version 的对象数组(按名称字典序),changed 是包含 name、from、to 的对象数组(按名称字典序),unchanged 是两边都有且版本相同的条目数量。
发布说明回答不了的正是这个问题——有没有没人改过却增加了的东西。请分别查看名称集合的差集和交集。