Generating CycloneDX software bill of materials with Anchore Syft


Software bill of materials aka SBOM is a critical tool in protecting software and software supply chain from potential security vulnerabilities and supply chain attacks. An SBOM includes all software components that are used to make a final software product. An SBOM is a document that describes details of the software version, patches, dependencies, vulnerabilities, licenses, etc. There are different formats to document SBOMs, and the industry standard formats right now are CycloneDX and SPDX. CycloneDX is from the OWASP foundation and is more focused on vulnerability and security. SPDX is from the Linux Foundation and is more focused on licensing and compliance.
In this article, we will look into the basics of the CycloneDX format by generating an SBOM file and going through various entries in the document.
The CycloneDX Format
The CycloneDX SBOM specification has a comprehensive object model that can capture software components, services, interdependencies, and relationships across all inventory types including software, hardware, and other digital assets. The object model can support detailed metadata of all components and services, life cycle stages, etc. The framework is also very extensible and modular and can represent a wide range of supply chain entities and metadata.
Let us look at some of the high-level components of the CycloneDX specification:
Top Level BOM Metadata - Contains basic information that describes the BOM itself, the tooling used, etc
Components - Components could be software, hardware, ML models, source code, configuration, etc. Includes the first-party and third-party components involved along with the metadata of each component
Services - External APIs that the software may call
Dependencies - Represent the graphs of the dependency of components on other components. It can represent direct and transitive dependencies
There are several other top-level entities in the specification - an overview is available at ttps://cyclonedx.org/specification/overview/
The following visual representation will help you get a better idea of the object model:
https://cyclonedx.org/images/CycloneDX-Object-Model-Swimlane.svg
Anchore Syft for SBOM generation
Syft is a comprehensive open-source CLI and library for generating Software Bill of Materials (SBOMs) for container images and filesystems. Syft development is sponsored by the security company Anchore
Some of the notable features of syft are:
Generates SBOMs for container images, filesystems, archives, and more to discover packages and libraries
Supports OCI, Docker, and Singularity image formats
Linux distribution identification
SBOM signing/attestation with in-toto specification
Support multiple SBOM formats - CycloneDX, SPDX, and Syft's proprietary format.
Generating SBOMs with Syft
We will demonstrate syft by generating SBOM for a container image. To begin with, install the soft binary by following the instructions at https://github.com/anchore/syft/. This will install the CLI “syft” on your OS.
The basis usage syntax is as follows:
syft [SOURCE] [FALGS]...
Syft can create SBOMs from a variety of sources including different formats for container images, different container daemons, filesystems and directories, etc. It supports a number of flags, one of the most important ones is the “-o” which can specify an output format and file.
Let us start by creating a CylconeDX formatted SBOM from the alpine docker image from the docker registry.
$syft alpine -o cyclonedx-json=alpine.cyclone.json
This will generate the SBOM file from the official docker repo at “docker.io/library/alpine” in the CycloneDX format and save it into the file alpine.cyclone.json. We will use the “jq” CLI to inspect the file and look at some of the important elements in the SBOM file.
Let us start by looking at the top-level elements of the file
$cat alpine.cyclone.json|jq '.|keys'
[
"$schema",
"bomFormat",
"components",
"dependencies",
"metadata",
"serialNumber",
"specVersion",
"version"
]
Looking at the important BOM-specific metadata
$cat alpine.cyclone.json |jq '{"$schema",bomFormat,serialNumber,specVersion,version}'
{
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"serialNumber": "urn:uuid:83201860-d58d-4a8e-8acb-59431eb61ce9",
"specVersion": "1.6",
"version": 1
}
The SBOM file is generated with the latest CycloneDX schema - version 1.6. Every SBOM will also have a serial number and a version. When you update the SBOM for the same source, the version should be incremented and the serialNumber should remain the same.
The SBOM metadata:
$cat alpine.cyclone.json|jq '.metadata'
{
"timestamp": "2024-12-01T01:10:11+05:30",
"tools": {
"components": [
{
"type": "application",
"author": "anchore",
"name": "syft",
"version": "1.17.0"
}
]
},
"component": {
"bom-ref": "c48d0ee842be961f",
"type": "container",
"name": "alpine",
"version": "sha256:37224ec0ba64192fa71cf0cd764a375e6204b58af5274f9d3b2984f9d5516cbb"
}
}
The metadata explains what kind of tools are used to create the SBOM as well as the source object (the component in the metadata ). As you can see, the type of component is “container” with the name “alpine” and the version, in this case, is the sha256 hash of the image.
Let us look at the ( software ) components in the BOM now.
$cat alpine.cyclone.json |jq -r '.components|keys[] as $k|[$k+1, .[$k].name, .[$k].type]|@tsv'|column -t -o " | "
1 | alpine-baselayout | library
2 | alpine-baselayout-data | library
3 | alpine-keys | library
4 | apk-tools | library
5 | busybox | library
6 | busybox-binsh | library
7 | ca-certificates-bundle | library
8 | libcrypto3 | library
9 | libssl3 | library
10 | musl | library
11 | musl-utils | library
12 | scanelf | library
13 | ssl_client | library
14 | zlib | library
15 | alpine | operating-system
Since Alpine is a lightweight container, it only has 15 components, out of which one is the operating system Alpine itself and the rest are the Alpine packages.
Now let us take a closer look at a single package, we will choose the ssl_client package.
$cat alpine.cyclone.json |jq '.components[12]|del(.properties)'
{
"bom-ref": "pkg:apk/alpine/ssl_client@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3&package-id=c5128491237ee638&upstream=busybox",
"type": "library",
"publisher": "Sören Tempel <soeren+alpine@soeren-tempel.net>",
"name": "ssl_client",
"version": "1.36.1-r29",
"description": "EXternal ssl_client for busybox wget",
"licenses": [
{
"license": {
"id": "GPL-2.0-only"
}
}
],
"cpe": "cpe:2.3:a:ssl-client:ssl-client:1.36.1-r29:*:*:*:*:*:*:*",
"purl": "pkg:apk/alpine/ssl_client@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3&upstream=busybox",
"externalReferences": [
{
"url": "https://busybox.net/",
"type": "distribution"
}
]
}
One thing to note particularly is the “bom-ref” key which is a unique key for an element within the BOM document. This ID can be used to refer to this component anywhere within the SBOM document. Most of the other keys are self-explanatory. The CPE and PURL keys are common standards used to specify the location of a package.
Let us also look at a few properties of the package.
$cat alpine.cyclone.json |jq '.components[12].properties'
[
{
"name": "syft:package:foundBy",
"value": "apk-db-cataloger"
},
{
"name": "syft:package:type",
"value": "apk"
},
{
"name": "syft:package:metadataType",
"value": "apk-db-entry"
},
{
"name": "syft:location:0:layerID",
"value": "sha256:75654b8eeebd3beae97271a102f57cdeb794cc91e442648544963a7e951e9558"
},
{
"name": "syft:location:0:path",
"value": "/lib/apk/db/installed"
},
{
"name": "syft:metadata:installedSize",
"value": "28672"
},
{
"name": "syft:metadata:originPackage",
"value": "busybox"
},
{
"name": "syft:metadata:provides:0",
"value": "cmd:ssl_client=1.36.1-r29"
},
{
"name": "syft:metadata:pullChecksum",
"value": "Q1fihnCSoO3udDb3DkQwtrfd42MJQ="
},
{
"name": "syft:metadata:pullDependencies:0",
"value": "so:libc.musl-x86_64.so.1"
},
{
"name": "syft:metadata:pullDependencies:1",
"value": "so:libcrypto.so.3"
},
{
"name": "syft:metadata:pullDependencies:2",
"value": "so:libssl.so.3"
},
{
"name": "syft:metadata:size",
"value": "4693"
}
]
Please note that I have removed a few entries to shorten the list, the keys are mostly self-explanatory.
One of the key elements in SBOMs is the license of the component. It is common for one component to have multiple licenses as part of the software might be built from different software dependencies - all of which might have different licenses that specify how they are used in another product. Most of the Alpine packages in the SBOM had simpler licenses, so to demonstrate this point, I generated an SBOM from the Ubuntu image. Let us look at the bash package, which has multiple licenses.
$cat ubuntu.cyclone.json|jq '.components[]|select(.name=="bash")|.licenses'
[
{
"license": {
"id": "BSD-4-Clause-UC"
}
},
{
"license": {
"id": "GFDL-1.3-only"
}
},
{
"license": {
"id": "GPL-2.0-only"
}
},
{
"license": {
"id": "GPL-2.0-or-later"
}
},
{
"license": {
"id": "GPL-3.0-only"
}
},
{
"license": {
"id": "GPL-3.0-or-later"
}
},
{
"license": {
"id": "Latex2e"
}
},
{
"license": {
"name": "GFDL-NIV-1.3"
}
},
{
"license": {
"name": "MIT-like"
}
},
{
"license": {
"name": "permissive"
}
}
]
As you can see, there are multiple licenses associated with bash.
Next, we will look at one of the most important parts of the SBM - software dependencies. We will examine the dependencies of the ssl_client package
$cat alpine.cyclone.json |jq '.dependencies[]|select(.ref=="pkg:apk/alpine/ssl_client@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3&package-id=c5128491237ee638&upstream=busybox")'
{
"ref": "pkg:apk/alpine/ssl_client@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3&package-id=c5128491237ee638&upstream=busybox",
"dependsOn": [
"pkg:apk/alpine/libcrypto3@3.3.2-r0?arch=x86_64&distro=alpine-3.20.3&package-id=0bd67c24de5c4187&upstream=openssl",
"pkg:apk/alpine/libssl3@3.3.2-r0?arch=x86_64&distro=alpine-3.20.3&package-id=409f5b93e7b861be&upstream=openssl",
"pkg:apk/alpine/musl@1.2.5-r0?arch=x86_64&distro=alpine-3.20.3&package-id=3ea0974d202d0c73"
]
}
If you see here, the dependency entry for the ssl_client package, all dependencies are referenced using their “bom-ref” ID. This helps in building a dependency graph of all packages within the SBOM.
As you can see, an SBOM provides a lot of valuable information about your application/containers that helps you with security audits, supply chain security, license compliances, etc.
Syft is just one tool that can help you generate an SBOM. There are more such tools in the SBOM ecosystem. A selected list of tools can be found at the CycloneDX tools center - https://cyclonedx.org/tool-center/
Subscribe to my newsletter
Read articles from Safeer C M directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
