BuildKit SBOM 生成与接入实践:从镜像扫描到 SPDX 证据链
【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit
SBOM(Software Bill of Materials,软件物料清单)用于记录构成最终镜像的软件包及其所属文件,是供应链安全与漏洞扫描的基础数据。本文基于 BuildKit 仓库中 docs/attestations/sbom.md 展开,讲解如何用attest:sbom选项在构建时自动生成 SBOM、如何通过 Dockerfile 构建参数扩展扫描范围(构建上下文与中间阶段)、以及 SBOM 以 in-toto attestation + SPDX JSON 形式挂载到镜像索引的具体结构与字段含义。读完本文,你将掌握从buildctl命令行触发扫描、自定义生成器镜像、解读输出证据到定位 80 MiB 大小限制的完整实战能力。
什么是 BuildKit 的 SBOM
BuildKit 在构建镜像时自动生成 SBOM,记录最终镜像由哪些软件包组成、这些软件包分别"拥有"哪些文件。除了文件与包清单,SBOM 通常还携带每个组件的元数据,例如软件许可证、作者以及可用于漏洞扫描的唯一包标识符(CPE、PURL 等)。
在 BuildKit 中,所有生成的 SBOM 都被包装在 in-toto attestation 中,predicate 采用 SPDX JSON 格式(https://spdx.dev/Document)。SBOM 的生成工作由遵循 SBOM 生成器协议 的"生成器镜像"(generator image)完成;当最终导出格式为容器镜像时,SBOM 通过 attestation 存储 挂载到镜像索引中。当前受支持的 attestation 类型除 SBOM 外还有 SLSA Provenance,二者统一由 docs/attestations/README.md 管理。
快速开始:构建带 SBOM 的镜像
使用内置默认扫描器
使用内置默认扫描器(即 docker/buildkit-syft-scanner,基于 syft 实现)时,只需要在buildctl build中追加attest:sbom选项,值为空字符串即可:
buildctl build \ --frontend=dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --opt attest:sbom=指定自定义 SBOM 生成器镜像
如果希望使用自己的扫描器(例如内部安全团队维护的扫描镜像),通过generator=参数指定镜像引用:
buildctl build \ --frontend=dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --opt attest:sbom=generator=<registry>/<image>生成器镜像必须遵循 SBOM 生成器协议:BuildKit 会把目标文件系统以只读挂载方式传给该镜像,由镜像把扫描结果写入指定目录。目前协议只支持 SPDX JSON 格式的 SBOM。
生成器镜像的协议约束
从源码 frontend/attestations/sbom/sbom.go 可以看到协议在实现层面对生成器镜像的约束(CreateSBOMScanner函数):
- 扫描器镜像必须带有
Entrypoint或Cmd,否则会报错scanner %s does not have cmd; - BuildKit 会向生成器注入以下环境变量:
BUILDKIT_SCAN_DESTINATION(必填):扫描结果输出目录,镜像内固定为/run/out/,扫描器应将 SBOM 写入$BUILDKIT_SCAN_DESTINATION/<scan>.spdx.json;BUILDKIT_SCAN_SOURCE(必填):主扫描目标,即最终构建结果根文件系统,镜像内固定为/run/src/core/sbom,输出文件名为$(basename $BUILDKIT_SCAN_SOURCE).spdx.json;BUILDKIT_SCAN_SOURCE_EXTRAS(可选):附加扫描目标(构建上下文或其他阶段)的根文件系统目录,镜像内固定为/run/src/extras/,未设置或为空时扫描器不应扫描 extras;
- 扫描器不允许在可选参数未设置时报错,也不允许为协议指定之外的文件系统产出 SBOM。
源码中常量CoreSBOMName = "sbom"、ExtraSBOMPrefix = "sbom-"表明:主扫描的挂载点为/run/src/core/sbom,每个附加目标以sbom-<目标名>的形式挂载到/run/src/extras/下,最终产物通过llb.AddMount(outDir, llb.Scratch())收集。另外,sbom_test.go 中的TestScannerTmpMount验证了扫描器临时目录的挂载方式:Linux 平台使用 tmpfs(MountType_TMPFS),Windows 平台使用 bind 挂载(MountType_BIND)并设置SkipOutput,即扫描器的中间产物不会进入最终镜像。
Dockerfile 配置:扩展扫描范围
默认情况下,只有最终构建结果会被扫描。由于多阶段构建中的中间阶段和构建上下文可能安装了构建期依赖(例如编译工具链、go mod download的缓存等),仅扫描最终阶段会让这些依赖被遗漏,进而漏报可能影响最终产物的漏洞。
BuildKit 为此提供了两个特殊的构建参数:
BUILDKIT_SBOM_SCAN_CONTEXT:额外扫描构建上下文;BUILDKIT_SBOM_SCAN_STAGE:额外扫描其他构建阶段。
这两个参数是"特殊值",不会参与变量替换,也不能作为 Dockerfile 内的环境变量使用——它们存在的唯一目的就是改变扫描器的行为。
参数取值
两个参数均可作为全局 meta 参数(在第一个FROM之前声明)或按阶段单独声明。若在全局声明,其值会传播给 Dockerfile 中的每个阶段。可取以下值:
| 值 | 含义 | 示例 |
|---|---|---|
true | 启用上下文/阶段扫描 | BUILDKIT_SBOM_SCAN_STAGE=true |
false | 禁用上下文/阶段扫描 | BUILDKIT_SBOM_SCAN_STAGE=false |
<stage-name>[,<stage-name>] | 仅扫描逗号分隔列表中列出的阶段 | BUILDKIT_SBOM_SCAN_STAGE=x,y表示只扫描名为x和y的阶段 |
需要注意:即使通过构建参数启用了扫描,从未被实际构建的阶段也永远不会被扫描(例如测试用例dockerfile_sbom_test.go中base2阶段因RUN命令故意失败而未产出扫描结果)。
示例:扫描中间构建阶段与构建上下文
FROM alpine:latest as build # 为中间构建阶段启用扫描 ARG BUILDKIT_SBOM_SCAN_STAGE=true WORKDIR /src COPY . . RUN ... # 构建某些软件 FROM scratch as final # 仅当构建完整执行到结束时才扫描构建上下文 ARG BUILDKIT_SBOM_SCAN_CONTEXT=true COPY --from=build /path/to/software /path/to/software命令行覆盖
也可以在命令行直接覆盖这些ARG,无需修改 Dockerfile:
buildctl build \ --frontend=dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --opt build-arg:BUILDKIT_SBOM_SCAN_STAGE=<value> \ --opt build-arg:BUILDKIT_SBOM_SCAN_CONTEXT=<value> \ --opt attest:sbom=注意:命令行覆盖只会作用于 Dockerfile 中已经显式声明的ARG定义。也就是说,如果某阶段没有声明BUILDKIT_SBOM_SCAN系列参数,命令行传值不会强制对该阶段启用扫描。集成测试 frontend/dockerfile/dockerfile_sbom_test.go 分别验证了"全局/阶段 ARG 开启扫描后产出 1 份 core + 3 份 extra attestation"与"命令行传false关闭后只剩 1 份 core attestation"两种行为。
底层解析逻辑
在 frontend/dockerfile/dockerfile2llb/convert.go 中,两个参数被声明为特殊参数并排除在普通变量替换之外:
sbomScanContext = "BUILDKIT_SBOM_SCAN_CONTEXT" sbomScanStage = "BUILDKIT_SBOM_SCAN_STAGE"dispatch阶段会遍历每个阶段,依次检查全局参数(d.opt.globalArgs)与阶段内声明的构建参数(d.buildArgs),并通过isEnabledForStage(d.stageName, v)解析true/false/ 阶段名列表三种取值;任一来源命中即为该阶段开启scanContext或scanStage。这印证了文档中"全局传播、按阶段生效、仅覆盖已声明 ARG"的行为描述。
输出:如何查看与解读 SBOM
构建完成后,可以通过docker buildx imagetools探索 registry 中的镜像,按 attestation 存储 描述的格式查看挂载的 SBOM 证据(镜像索引中platform为unknown/unknown的 manifest 即 attestation manifest,其in-toto.io/predicate-type注解为https://spdx.dev/Document)。
以下是一个基于alpine:latest的简单镜像生成的 SBOM 示例(具体内容取决于生成器,此处展示典型结构):
{ "_type": "https://in-toto.io/Statement/v1", "predicateType": "https://spdx.dev/Document", "subject": [ { "name": "pkg:docker/<registry>/<image>@<tag/digest>?platform=<platform>", "digest": { "sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862" } } ], "predicate": { "SPDXID": "SPDXRef-DOCUMENT", "name": "/run/src/core", "spdxVersion": "SPDX-2.2", "creationInfo": { "created": "2022-11-09T10:12:01.338817553Z", "creators": [ "Organization: Anchore, Inc", "Tool: syft-[not provided]" ], "licenseListVersion": "3.18" }, "dataLicense": "CC0-1.0", "documentNamespace": "https://anchore.com/syft/dir/run/src/core-4006bb64-24b1-4a22-a18f-94efc6b90edb", "files": [ { "SPDXID": "SPDXRef-1ac501c94e2f9f81", "comment": "layerID: sha256:9b18e9b68314027565b90ff6189d65942c0f7986da80df008b8431276885218e", "fileName": "/bin/busybox", "licenseConcluded": "NOASSERTION" }, ... ], "packages": [ { "SPDXID": "SPDXRef-980737451f148c56", "description": "Size optimized toolbox of many common UNIX utilities", "downloadLocation": "https://busybox.net/", "externalRefs": [ { "referenceCategory": "SECURITY", "referenceLocator": "cpe:2.3:a:busybox:busybox:1.35.0-r17:*:*:*:*:*:*:*", "referenceType": "cpe23Type" }, { "referenceCategory": "PACKAGE_MANAGER", "referenceLocator": "pkg:alpine/busybox@1.35.0-r17?arch=aarch64&upstream=busybox&distro=alpine-3.16.2", "referenceType": "purl" } ], "filesAnalyzed": false, "hasFiles": [ "SPDXRef-1ac501c94e2f9f81", ... ], "licenseConcluded": "GPL-2.0-only", "licenseDeclared": "GPL-2.0-only", "name": "busybox", "originator": "Person: Sören Tempel <soeren+alpine@soeren-tempel.net>", "sourceInfo": "acquired package info from APK DB: lib/apk/db/installed", "versionInfo": "1.35.0-r17" }, ... ], "relationships": [ { "relatedSpdxElement": "SPDXRef-1ac501c94e2f9f81", "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-980737451f148c56" }, ... ] } }输出字段解读
具体输出取决于生成器,但通常遵循以下规律:
files键:镜像中所有文件的列表;packages键:从镜像中发现的所有软件包列表;relationships键:把文件与包关联起来,并描述它们之间的关系(例如示例中的CONTAINS关系,表示包busybox包含文件/bin/busybox);files与packages中的条目若带有comment字段,则该字段包含引入该条目的镜像层的sha256digest(前提是该层存在于最终镜像中)——通过这个字段可以把 SBOM 中的每个包/文件精确回溯到具体镜像层,方便定位"哪个 RUN 命令引入了哪个依赖"。
从 in-toto 层面看,外层subject的name形如pkg:docker/<registry>/<image>@<tag/digest>?platform=<platform>,digest指向被证明的目标镜像 manifest,与 attestation-storage.md 中"attestation 的 subject 应设置为与目标 manifest 相同 digest"的约定一致。
80 MiB 大小限制
生成后的 SBOM attestation 文件在挂载到导出镜像前会被限制为80 MiB。SPDX JSON 文档包含详细的文件、包和关系元数据,大型镜像的 SBOM 可能逼近甚至超过该上限。这一限制的实现位于 exporter/attestation/make.go 的常量maxAttestationBytes int64 = 80 << 20(即 80 × 1024 × 1024 字节),用于防止导出器对前端提供的 attestation 文件进行无界读取。如果你的镜像包体量很大,需要留意扫描产物是否触及该上限。
在 OCI 镜像索引中的存储形态
虽然 SBOM 的生成入口是attest:sbom,但其"落盘"方式是理解整条证据链的关键。按 attestation-storage.md 的说明,SBOM 会以 OCI artifact 的形式存储(可设置镜像导出选项oci-artifact=false回退到传统格式):
- 根镜像索引的
manifests中,在所有可运行 manifest 之后追加 attestation manifest,其platform固定为unknown/unknown,防止容器运行时误拉取或误运行; - attestation manifest 的
artifactType为application/vnd.docker.attestation.manifest.v1+json,subject指向目标镜像 manifest,layers中每个层是一份 attestation blob(mediaType为application/vnd.in-toto+json),并带in-toto.io/predicate-type注解以便遍历时按 predicate 类型快速筛选; - 镜像索引的 manifest descriptor 上带有
vnd.docker.reference.type=attestation-manifest与vnd.docker.reference.digest注解,用于把 attestation manifest 与其所证明的镜像 manifest 关联起来。
这一存储格式对docker buildx imagetools、漏洞扫描器等消费方是公开契约,SBOM 可以只按需拉取(利用 predicate-type 注解跳过无关 attestation),而无需拉取镜像全部内容。
小结
BuildKit 的 SBOM 能力可以概括为一条闭环链路:attest:sbom选项指定生成器 → 生成器按协议扫描最终 rootfs 与可选的上下文/阶段 → 产物以 SPDX JSON 写入并在导出时被包装为 in-toto attestation → 以 OCI artifact 形式挂载到镜像索引 → 消费方通过 predicate-type 注解按需读取。配合BUILDKIT_SBOM_SCAN_CONTEXT/BUILDKIT_SBOM_SCAN_STAGE构建参数,可以决定扫描边界,避免漏掉构建期依赖引入的漏洞。相关实现与测试可直接在仓库内查看:SBOM 扫描协议、attestation 存储格式、扫描器调用实现、Dockerfile 参数解析、端到端集成测试 与 大小限制实现。
【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考