作为 CI 的一部分,我想生成 SBOM 并将其上传到依赖项轨道。我希望对我想要跟踪的所有存储库采用一种方法。它们可以包含多种技术 - Nodejs、Python、Go 等。甚至一个存储库也可以包含更多依赖锁定文件。
对于 NodeJS 项目,我有 package.json 和yarn.lock。该存储库还包含生成的文档和一些 JS 文件。并包含一些帮助脚本。
因此,我尝试使用 cdxgen,它支持所有内容,甚至可以找到 js 文件并解析其内容来猜测库。这可以很好地扫描各处的所有依赖项。问题是它无法识别直接依赖与传递依赖。
然后我尝试在主项目上使用Vite的插件。它没有找到所有依赖项,并且无法识别任何有关直接依赖项的信息。
最好的结果是使用 cyclonedx-node-yarn,它生成了正确的传递依赖与直接依赖,它看起来组件列表也是正确的。它只是比那里的其他选项慢得多。
我遇到的 cyclonedx-node-yarn 问题是,我必须为不同的技术运行不同的包,并将它们合并到一个 SBOM 中。我在这里错过了什么吗?是否有一种方法可以更轻松地自动将任何技术的所有依赖项生成到每个文件夹及其子文件夹的一个文件中?这是否意味着每个项目编写自己的脚本来生成这些?
考虑查看 Syft,这是一个开源 SBOM 生成器,可以扫描包含来自不同生态系统的软件的项目文件夹。它可以以 CycloneDX 格式输出 SBOM。
Syft 用 Go 编写,可作为预构建的单个二进制文件使用。有适用于 Windows、Mac、Linux 的软件包。有一个简单的安装程序 shell 脚本:
$ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
这将分析目录(或容器)并生成人类可读的 SBOM 作为文本表。请注意,这不是完整的输出,因为它很长。重现所有这些的步骤位于我答案的底部。
$ syft dir:./sbom-demo | (head; tail)
✔ Indexed file system sbom-demo
✔ Cataloged contents 5ce0d9d1e0410a9ea2edcd96c9fbf223f346c2e57ab044e60e706ba9d3d46eb0
├── ✔ Packages [112 packages]
├── ✔ File digests [32 files]
├── ✔ File metadata [32 locations]
└── ✔ Executables [3 executables]
[0000] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal)
NAME VERSION TYPE
accepts 1.3.8 npm
array-flatten 1.1.1 npm
blinker 1.9.0 python
body-parser 1.20.3 npm
bytes 3.1.2 npm
call-bind-apply-helpers 1.0.1 npm
call-bound 1.0.3 npm
certifi 2024.12.14 python
charset-normalizer 3.4.0 python
Syft 支持多种输出格式,包括
cyclonedx-json
和 cyclonedx-xml
。
$ syft dir:./sbom-demo -o cyclonedx-json > sbom.json
✔ Indexed file system ./sbom-demo
✔ Cataloged contents cdb4ee2aea69cc6a83331bbe96dc2caa9a299d21329efb0336fc02a82e1839a8
├── ✔ Packages [112 packages]
├── ✔ File digests [32 files]
├── ✔ File metadata [32 locations]
└── ✔ Executables [3 executables]
[0000] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal)
在这里,我们将使用官方的 sbom-utility 验证器来确保 SBOM 符合规范。
$ sbom-utility validate -i sbom.json
Welcome to the sbom-utility! Version 'v0.17.1-pre' (sbom-utility) (linux/amd64)
===============================================================================
[INFO] Loading (embedded) default schema config file: 'config.json'...
[INFO] Loading (embedded) default license policy file: 'license.json'...
[INFO] Attempting to load and unmarshal data from: 'sbom.json'...
[INFO] Successfully unmarshalled data from: 'sbom.json'
[INFO] Determining file's BOM format and version...
[INFO] Determined BOM format, version (variant): 'CycloneDX', '1.6' (latest)
[INFO] Matching BOM schema (for validation): schema/cyclonedx/1.6/bom-1.6.schema.json
[INFO] Loading schema 'schema/cyclonedx/1.6/bom-1.6.schema.json'...
[INFO] Found schema dependencies: [jsf-0.82.schema.json spdx.schema.json]
[INFO] Added schema 'schema/cyclonedx/common/jsf-0.82.schema.json' to loader:...
[INFO] Added schema 'schema/cyclonedx/common/spdx.schema.json' to loader:...
[INFO] Compiling schema: 'schema/cyclonedx/1.6/bom-1.6.schema.json'...
[INFO] Schema 'schema/cyclonedx/1.6/bom-1.6.schema.json' loaded
[INFO] Validating 'sbom.json'...
[INFO] BOM valid against JSON schema: 'true'
看起来不错
在这里,我们通过从 go、node 和 python 生态系统中查找包来获取数字,表明它正在做正确的事情。
$ for p in golang npm pypi; do \
echo -n $p; \
jq . < sbom.json \
| grep "purl\": \"pkg:$p" \
| sort | uniq | wc -l ; done
golang 28
npm 70
pypi 12
希望有帮助。
下面就是我为上面的演示所做的设置。我将其放在这里只是为了完整性和可重复性。
# Create main directory
mkdir sbom-demo
cd sbom-demo
# Set up Node.js project
mkdir nodejs-app
cd nodejs-app
cat > package.json << 'EOL'
{
"name": "nodejs-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
}
}
EOL
npm install
cd ..
# Set up Python project
mkdir python-app
cd python-app
cat > requirements.txt << 'EOL'
requests==2.31.0
Flask==3.0.0
EOL
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
deactivate
cd ..
# Set up Go project
mkdir go-app
cd go-app
go mod init example.com/go-app
cat > main.go << 'EOL'
package main
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func main() {
logger := logrus.New()
logger.Info("Starting application")
}
EOL
go mod tidy
cd ..