ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Podman 多扫描器 SBOM 输出合并策略:`--sbom-merge-strategy` 参数完全解析

2026/9/20 18:04:41 拓冰建站 浏览量
Podman 多扫描器 SBOM 输出合并策略:`--sbom-merge-strategy` 参数完全解析 Podman 多扫描器 SBOM 输出合并策略--sbom-merge-strategy参数完全解析【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman在 Podman 的podman farm build构建流程中SBOM软件物料清单Software Bill of Materials生成是一个重要环节构建出的镜像会被扫描器如 Syft、Trivy分析产出 CycloneDX 或 SPDX 格式的 JSON 清单。当一次构建同时使用多个--sbom-scanner-command扫描命令时就会产生多份 SBOM 输出需要一套明确的规则把它们合并成一份最终结果。本文将以 docs/source/markdown/options/sbom-merge-strategy.md 为核心结合仓库源码深入讲解--sbom-merge-strategy参数的三种取值、合并的底层实现、参数校验规则以及实际用法帮助你精确控制多扫描器场景下的 SBOM 产出。一、为什么需要合并策略多扫描命令的产生背景在 Podman 的构建体系中SBOM 生成由一组--sbom-*选项控制见 sbom.md。其中--sbom-scanner-command允许用户重复指定多次每次指定一条要在扫描器镜像中执行的命令{ROOTFS}构建出的镜像文件系统根目录bind mount 挂载{CONTEXT}构建上下文及附加构建上下文bind mount 挂载{OUTPUT}一个临时输出文件的名称会被读取后与其他输出合并或复制到别处例如扫描根文件系统和构建上下文这两个不同来源时就会写两条扫描命令podman farm build \ --sbom-scanner-command/syft scan -q dir:{ROOTFS} --output cyclonedx-json{OUTPUT} \ --sbom-scanner-command/syft scan -q dir:{CONTEXT} --output cyclonedx-json{OUTPUT} \ ...由于多条命令各自产出一份 SBOM JSON 文档最终必须决定如何把后来的文档合并进先前的文档。这正是--sbom-merge-strategy的职责所在。从源码结构看Podman 的 SBOM 扫描能力构建在 vendored 的 Buildah 之上核心数据结构SBOMScanOptions定义于 vendor/go.podman.io/buildah/define/types.go其中MergeStrategy字段类型为SBOMMergeStrategy即对应本参数// SBOMScanOptions encapsulates options which control whether or not we run a // scanner on the rootfs that were about to commit, and how. type SBOMScanOptions struct { ... Commands []string // one or more commands to invoke for the image rootfs or ContextDir locations MergeStrategy SBOMMergeStrategy // how to merge the outputs of multiple scans }在 CLI 侧cmd/podman/common/build.go 会检测任一--sbom-*标志是否被设置一旦命中就调用parse.SBOMScanOptions()解析出上述结构并把构建上下文目录补充进ContextDir。二、--sbom-merge-strategy参数说明该参数语法为--sbom-merge-strategymethod适用前提只有当使用了多于一个--sbom-scanner-command值时才有意义。此时后执行的命令产出的 SBOM 文档会按照指定的 method与先执行命令产出的文档进行合并。官方文档明确列出三种可识别取值取值合并行为cat直接将文件内容拼接concatenate在一起merge-cyclonedx-by-component-name-and-version合并 JSON 文档的component组件字段当某组nameversion组合已经出现过时忽略该条记录merge-spdx-by-package-name-and-versioninfo合并 JSON 文档的package包字段当某组nameversionInfo组合已经出现过时忽略该条记录文档处理顺序即扫描命令的指定顺序先执行命令的文档作为基底base后执行命令的文档作为待合并对象merge按序逐一并入。三、三种策略的底层实现剖析1.cat纯字节级拼接这是最朴素也最快的策略。在 vendor/go.podman.io/buildah/internal/sbom/merge.go 中cat分支的实现是直接用io.Copy把后一个文件以追加os.O_APPEND方式写入前一个文件末尾case define.SBOMMergeStrategyCat: dst, err : os.OpenFile(inputOutputSBOM, os.O_RDWR|os.O_APPEND, 0o644) ... src, err : os.Open(inputSBOM) ... if _, err io.Copy(dst, src); err ! nil { return err }注意事项cat不做任何 JSON 解析也不做去重。如果多个扫描器都输出了结构完整的 JSON 文档直接拼接后得到的文件将不再是合法的单一 JSON 文档。它更适用于输出本身是非结构化文本、或者后续由外部工具再次处理的情形。2. CycloneDX按nameversion去重合并components该策略面向 CycloneDX JSON 格式。实现位于 vendor/go.podman.io/buildah/internal/sbom/merge.go用decodeJSON把基底文档与待合并文档分别读入map[string]any调用mergeSlicesWithoutDuplicates(base, merge, components, getComponentNameVersionPurl)即先遍历基底文档components数组提取每个组件的nameversion组合作为唯一键并顺带收集purl值再遍历待合并文档的components只有当某组件的nameversion组合在基底中从未出现过时才把它追加进去同时在合并过程中收集去重后的 package URLpurl列表用于生成 PURL 清单文件最后用encodeJSON把合并后的文档写回输出文件。其去重核心mergeSlicesWithoutDuplicates的实现思路是维护一个uniqueKeys集合先登记基底切片中所有记录的键再遍历待合并切片键未出现过才追加并登记从而保证先到先得、后到者去重见 merge.go。对应地SBOMMergeStrategy常量在 vendor/go.podman.io/buildah/define/types.go 中的官方注释为// SBOMMergeStrategyCycloneDXByComponentNameAndVersion adds components // from the second document to the first, so long as they have a // nameversion combination which is not already present in the // components array. SBOMMergeStrategyCycloneDXByComponentNameAndVersion SBOMMergeStrategy merge-cyclonedx-by-component-name-and-version3. SPDX按nameversionInfo去重合并packages该策略面向 SPDX JSON 格式逻辑与 CycloneDX 分支对称但处理的是packages字段且唯一键由nameversionInfo组成见 merge.go。此外SPDX 分支还多做了一步额外合并hasExtractedLicensingInfos列表按licenseId去重——也就是把后一个文档中提取出的自定义许可证信息并入基底文档只要其licenseId尚未出现。对应常量注释define/types.go明确指出这一行为// SBOMMergeStrategySPDXByPackageNameAndVersionInfo adds packages from // the second document to the first, so long as they have a // nameversionInfo combination which is not already present in the // first documents packages array, and adds hasExtractedLicensingInfos // items from the second document to the first, so long as they include // a licenseId value which is not already present in the first // documents hasExtractedLicensingInfos array. SBOMMergeStrategySPDXByPackageNameAndVersionInfo SBOMMergeStrategy merge-spdx-by-package-name-and-versioninfo注意两种按名去重策略都依赖 JSON 结构解析要求扫描器输出的是结构合法的 CycloneDX / SPDX JSON 文档若待合并文档结构不完整对应的字段会按空切片处理不影响合并流程正常结束。四、合并的执行顺序谁先谁后合并的编排逻辑位于 Buildah 的扫描主流程 vendor/go.podman.io/buildah/scan.go第一份结果文件直接复制copyFile为最终 SBOM 输出的初始版本同时调用一次sbom.Merge以便生成 PURL 文件其后的每一份结果文件调用sbom.Merge(scanSpec.MergeStrategy, sbomResult, thisResultFile, purlResult)把新文档合并进已累积的最终输出。也就是说最终文档内容完全取决于扫描命令的书写顺序——最先出现的命令定义基底后续命令的结果按序并入。这与文档中Documents are processed in the order in which they are generated, which is the order in which the commands that generate them were specified的描述完全一致。五、参数校验规则何时必须指定、何时会报错从解析函数 vendor/go.podman.io/buildah/pkg/parse/parse.go 可以看到--sbom-merge-strategy的取值合法性校验非常严格多命令但未给策略 → 报错if len(options.Commands) 1 options.MergeStrategy { return options, fmt.Errorf(sbom configuration included multiple %q values but no %q value, --sbom-scanner-command, --sbom-merge-strategy) }即只要--sbom-scanner-command出现超过一次就必须同时提供--sbom-merge-strategy。未知策略值 → 报错switch只放行以下三种其余一律报unrecognized merge strategydefine.SBOMMergeStrategyCatcatdefine.SBOMMergeStrategyCycloneDXByComponentNameAndVersiondefine.SBOMMergeStrategySPDXByPackageNameAndVersionInfo配套约束SBOM 配置还必须同时满足指定了扫描器镜像--sbom-scanner-image与至少一条--sbom-scanner-command以及至少指定一个输出目标--sbom-output/--sbom-image-output/--sbom-purl-output/--sbom-image-purl-output否则同样报错。在 Podman CLI 侧这一系列的标志解析入口在 cmd/podman/common/build.go当任一--sbom、--sbom-scanner-command、--sbom-scanner-image、--sbom-merge-strategy、--sbom-output、--sbom-image-output、--sbom-purl-output、--sbom-image-purl-output标志被修改时即构造SBOMScanOptions并追加到构建选项列表。六、与--sbom预设的对应关系Podman 为常见扫描器提供了--sbom预设preset其中默认就绑定了对应的合并策略见 sbom.md预设值等价参数展开节选syft/syft-cyclonedx--sbom-scanner-imageghcr.io/anchore/syft两条--sbom-scanner-command分别扫{ROOTFS}与{CONTEXT}CycloneDX 输出--sbom-merge-strategymerge-cyclonedx-by-component-name-and-versionsyft-spdx同上但输出spdx-json--sbom-merge-strategymerge-spdx-by-package-name-and-versioninfotrivy/trivy-cyclonedx--sbom-scanner-imageghcr.io/aquasecurity/trivy两条命令分别扫{ROOTFS}与{CONTEXT}--format cyclonedx合并策略同上CycloneDX 版trivy-spdxTrivy 输出spdx-json合并策略为 SPDX 版因此多数场景下你无需手动写合并策略——选用预设即可。只有当你自定义多条--sbom-scanner-command时才必须亲手指定--sbom-merge-strategy。七、自定义多扫描命令的完整示例示例 1Syft 同时扫描根文件系统与构建上下文CycloneDXpodman farm build \ --tag registry.example.com/team/app:latest \ --sbom-scanner-imageghcr.io/anchore/syft \ --sbom-scanner-command/syft scan -q dir:{ROOTFS} --output cyclonedx-json{OUTPUT} \ --sbom-scanner-command/syft scan -q dir:{CONTEXT} --output cyclonedx-json{OUTPUT} \ --sbom-merge-strategymerge-cyclonedx-by-component-name-and-version \ --sbom-output./app-sbom.cdx.json \ .两份 CycloneDX JSON 会按序合并{ROOTFS}的扫描结果作为基底{CONTEXT}扫描结果中nameversion已存在的组件被跳过最终得到一份去重后的合法 CycloneDX 文档。示例 2Trivy 双扫描并输出 SPDXpodman farm build \ --tag registry.example.com/team/app:latest \ --sbom-scanner-imageghcr.io/aquasecurity/trivy \ --sbom-scanner-commandtrivy filesystem -q {ROOTFS} --format spdx-json --output {OUTPUT} \ --sbom-scanner-commandtrivy filesystem -q {CONTEXT} --format spdx-json --output {OUTPUT} \ --sbom-merge-strategymerge-spdx-by-package-name-and-versioninfo \ --sbom-output./app-sbom.spdx.json \ .合并时以nameversionInfo为键去重packages并按licenseId去重hasExtractedLicensingInfos。示例 3cat拼接仅适合非结构化/文本输出podman farm build \ --tag registry.example.com/team/app:latest \ --sbom-scanner-commandtool1 -o {OUTPUT} \ --sbom-scanner-commandtool2 -o {OUTPUT} \ --sbom-merge-strategycat \ --sbom-output./combined.txt \ .两份输出按字节顺序首尾相接。请务必确认输出内容本身可拼接如纯文本报告否则结果将不是合法 JSON。八、REST API 与 Bindings 中的对应字段该能力不止存在于 CLI。在兼容 API 层sbom-scanner-command与sbom-merge-strategy作为查询参数暴露pkg/api/handlers/compat/images_build.go 中定义了SBOMCommandsschema:sbom-scanner-command与SBOMMergeStrategyschema:sbom-merge-strategy两个查询字段并在 L718-L721 对sbom-scanner-command的重复传入做解析校验在 Go 绑定层pkg/bindings/images/build.go 会把每个扫描命令逐个添加为sbom-scanner-command参数并将非空的MergeStrategy设置为sbom-merge-strategy参数。这意味着通过 Docker 兼容 API 或 Podman Go Bindings 触发构建时同样可以传递多扫描命令与合并策略行为与 CLI 一致。九、最佳实践小结预设优先能使用--sbomsyft-cyclonedx、--sbomtrivy-spdx等预设时不必手写合并策略预设已内置与输出格式匹配的默认策略格式匹配合并策略必须与扫描器输出格式一致——CycloneDX JSON 配merge-cyclonedx-*SPDX JSON 配merge-spdx-*混用格式会导致字段无法识别、去重失效顺序即语义第一条--sbom-scanner-command定义基底后续命令按序合并去重规则为先到先得cat慎用仅当输出不是结构化 JSON 时才考虑cat否则会产生非法 JSON校验提前多条扫描命令 缺失合并策略、或写了不认识的策略值都会在参数解析阶段直接报错可据此快速定位配置问题。通过理解--sbom-merge-strategy的三种取值及其源码实现你可以把多扫描器的 SBOM 产出真正纳入可控范围为镜像供应链安全审计提供准确、无重复的软件物料清单。【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考