Home / SBOM / How to Generate an SBOM with Free Open Source Tools

How to Generate an SBOM with Free Open Source Tools

Updated on July 16, 2024
By: Anchore
Sample of JSON output from Syft
Navigate To
Close Table of Contents
Table of Contents

    Generating a Software Bill of Materials (SBOM) as part of the DevOps process is essential for securing the software supply chain. SBOMs are becoming critical due to the growing prominence of supply chain attacks such as Solarwinds, maintainers intentionally adding malware like node-ipc, and severe vulnerabilities like Log4Shell.

    SBOMs can help identify the software components used within a system as well as licenses and vulnerabilities. SBOMs also can be used to comply with the Executive Order Improving the Nation’s Cybersecurity.

    Fortunately, there are a number of tools that can help create SBOMs and generating your first one takes just a few easy steps:

    1. Choose your SBOM generation tool – we’ll use Syft here
    2. Download and install Syft
    3. Determine the SBOM output format you need
    4. Run Syft against the desired source: syft <source> -o <format>

    Hold on! Before you jump into using open source tools for SBOMs, note that you can get instant access to a free trial of the Anchore Enterprise platform here.

    Open Source Tools for Generating SBOMs

    Many tools are available for generating SBOMs, so the first thing to do is pick the right one. SBOM generators are often specific to a particular ecosystem, such as Python or Go. Some are capable of generating SBOMs for many different ecosystems and environments. Some of the more popular SBOM tools are:

    1. Syft by Anchore
    2. Tern
    3. Kubernetes BOM tool
    4. spdx-sbom-generator

    For this example, we’ll focus on Syft since it is easy to use in many different scenarios and supports a variety of ecosystems. Syft can run on a developer workstation, in CI systems, or as a Docker container. It can scan a wide variety of ecosystems, from Linux distributions to many types of build dependency specifications.

    Getting Syft

    The first thing to do is download Syft. There are a number of ways to do this:

    Using curl

    The recommended method to get Syft for macOS and Linux is by using curl:

    curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <SOME_BIN_PATH> <RELEASE_VERSION>

    For example:

    curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

    Homebrew

    For macOS, Syft is available to install from Homebrew:

    brew tap anchore/syft
    brew install syft

    Direct Download

    Directly download Syft binaries for many platforms, including Windows from the GitHub releases page.

    Docker

    There is also a Syft Docker image with every release: anchore/syft, which can be run like this:

    docker run -it --rm anchore/syft <args>

    Validate the Syft Installation

    To confirm Syft was installed correctly, simply run:

    syft version

    Which will produce output similar to:

    Application:    syft
    Version:        1.4.1
    BuildDate:      2024-05-09T19:45:46Z
    GitCommit:      c200896a9644f9b6bd4bc3785c848276c33bb53c
    GitDescription: v1.4.1
    Platform:       darwin/arm64
    GoVersion:      go1.21.9
    Compiler:       gc

    Note: Syft was version 1.4.1 at the time of this writing

    Generating Your First SBOM

    Once Syft is installed and available, creating an SBOM is simple. Syft supports multiple sources to scan when generating an SBOM using both the local filesystem and container images.

    Scanning Images

    To generate an SBOM for a Docker or OCI image – even without a Docker daemon, run:

    syft <image>

    By default, output includes only software included in the container’s final layer. To include software from all image layers in the SBOM, regardless of its presence in the final image, use the –scope all-layers option:

    syft --scope all-layers <image>

    Scanning the Filesystem

    To generate an SBOM for the local filesystem, use the dir: and file: prefixes with either absolute or relative paths. For example to scan the current directory:

    syft dir:.

    Or a specific file:

    syft file:/my-go-binary

    Syft can generate SBOMs from a variety of other sources, such as Podman, tar archives, or directly from an OCI registry even when Docker is not available. Check out the full list of sources.

    Basic Example

    For example, to scan the latest Alpine image, simply run:

    syft alpine:latest

    You should see output similar to this:

     ✔ Loaded image         alpine:latest
     ✔ Parsed image         sha256:ace17d5d883e9ea5a21138d0608d…
     ✔ Cataloged contents   064d96b7a2fc6398b4c596e2a693fcc961a…
       ├── ✔ Packages                        [15 packages]
       ├── ✔ File digests                    [80 files]
       ├── ✔ File metadata                   [80 locations]
       └── ✔ Executables                     [17 executables]
    NAME                    VERSION               TYPE
    alpine-baselayout       3.4.3-r2              apk
    alpine-baselayout-data  3.4.3-r2              apk
    alpine-keys             2.4-r1                apk
    apk-tools               2.14.0-r5             apk
    busybox                 1.36.1-r15            apk
    busybox-binsh           1.36.1-r15            apk
    ca-certificates-bundle  20230506-r0           apk
    libc-utils              0.7.2-r5              apk
    libcrypto3              3.1.4-r5              apk
    libssl3                 3.1.4-r5              apk
    musl                    1.2.4_git20230717-r4  apk
    musl-utils              1.2.4_git20230717-r4  apk
    scanelf                 1.3.7-r2              apk
    ssl_client              1.36.1-r15            apk
    zlib                    1.3.1-r0              apk

    By default, the SBOM you’ll see will be a nicely formatted table rather than any standardized SBOM format, which leads us to…

    Choose an SBOM Format

    Depending on the use cases, it may be important to select a particular SBOM format. The most common ones are Software Package Data Exchange (SPDX) and CycloneDX, both of which Syft supports. Syft also has a format which interoperates losslessly with the Grype vulnerability scanner.

    While Syft supports these different formats, they have slightly different goals and features. It may be important to pick SPDX or CycloneDX for interoperability with other tools or as a standardized format to distribute to downstream consumers.

    Generating an SBOM in SPDX format

    If the use case requires an SBOM in SPDX format, Syft has you covered. SPDX has been around the longest of all the formats mentioned here. There are multiple variants of SPDX. Syft supports SPDX Tag-value (spdx-tag-value) and SPDX JSON (spdx-json). For SPDX JSON, simply add the -o spdx-json argument. For example, running this against a docker image, again using the latest Alpine:

    syft alpine:latest -o spdx-json

    Notice there is a lot more data than the table view allows! Indeed, there’s more data than we can fit in this article. However, the above command should produce something resembling:

    {
      "spdxVersion": "SPDX-2.3",
      "dataLicense": "CC0-1.0",
      "SPDXID": "SPDXRef-DOCUMENT",
      "name": "alpine",
      "documentNamespace": "https://anchore.com/syft/image/alpine-0f87c686-5633-421b-b5e2-620ade3fd5f9",
      "creationInfo": {
        "licenseListVersion": "3.23",
        "creators": [
          "Organization: Anchore, Inc",
          "Tool: syft-1.4.1"
        ],
        "created": "2024-05-22T14:24:16Z"
      },
      "packages": [
        {
          "name": "alpine-baselayout",
          "SPDXID": "SPDXRef-Package-apk-alpine-baselayout-6cc16bbb16d67380",
          "versionInfo": "3.4.3-r2",
          "supplier": "Person: Natanael Copa ([email protected])",
          "originator": "Person: Natanael Copa ([email protected])",
          "downloadLocation": "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
          "filesAnalyzed": true,
          "packageVerificationCode": {
            "packageVerificationCodeValue": "6a22bff30e2aed347029eeb9d51c810613705455"
          },
          "sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
          "licenseConcluded": "NOASSERTION",
          "licenseDeclared": "GPL-2.0-only",
          "copyrightText": "NOASSERTION",
          "description": "Alpine base dir structure and init scripts",
          "externalRefs": [
            {
              "referenceCategory": "SECURITY",
              "referenceType": "cpe23Type",
              "referenceLocator": "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.4.3-r2:*:*:*:*:*:*:*"
            },
            {
              "referenceCategory": "PACKAGE-MANAGER",
              "referenceType": "purl",
              "referenceLocator": "pkg:apk/alpine/[email protected]?arch=aarch64&distro=alpine-3.19.1"
            }
          ]
        }
      ],
      "files": [
          {
          "fileName": "/lib/apk/db/installed",
          "SPDXID": "SPDXRef-File-lib-apk-db-installed-27a7c299f77baaa5",
          "checksums": [
            {
              "algorithm": "SHA1",
              "checksumValue": "0000000000000000000000000000000000000000"
            }
          ],
          "licenseConcluded": "NOASSERTION",
          "licenseInfoInFiles": [
            "NOASSERTION"
          ],
          "copyrightText": "",
          "comment": "layerID: sha256:b09314aec293bcd9a8ee5e643539437b3846f9e5e55f79e282e5f67e3026de5e"
        }
      ]
      "relationships": [
        {
          "spdxElementId": "SPDXRef-Package-apk-libc-utils-233bcb7cc2dfdc24",
          "relatedSpdxElement": "SPDXRef-File-lib-apk-db-installed-27a7c299f77baaa5",
          "relationshipType": "OTHER",
          "comment": "evident-by: indicates the package's existence is evident by the given file"
        }
      ]
    }

    Not only does this format contain the package names, but also Package URLs, license information, and a host of other things such as files Syft identified associated with a package.

    Generating an SBOM in CycloneDX format

    Similarly, if the SBOM is required in CycloneDX format, use that format option. Syft supports CycloneDX XML (cyclonedx-xml) and JSON (cyclonedx-json). For CycloneDX XML:

    syft <source> -o cyclonedx-xml

    To run this against the same latest Alpine image, run:

    syft alpine:latest -o cyclonedx-xml

    And you should see a result resembling this:

    <?xml version="1.0" encoding="UTF-8"?>
    <bom serialNumber="urn:uuid:27c36caa-2185-4827-a258-e2e21d6b4161" version="1" xmlns="http://cyclonedx.org/schema/bom/1.5">
      <metadata>
        <timestamp>2024-05-22T15:50:36+01:00</timestamp>
        <tools>
          <components>
            <component type="application">
              <author>anchore</author>
              <name>syft</name>
              <version>1.4.1</version>
            </component>
          </components>
        </tools>
        <component bom-ref="e2d2c65d3af2cda0" type="container">
          <name>alpine</name>
          <version>sha256:064d96b7a2fc6398b4c596e2a693fcc961a586734a40c9dd91225c84d801030a</version>
        </component>
      </metadata>
      <components>
        <component bom-ref="pkg:apk/alpine/[email protected]?arch=aarch64&distro=alpine-3.19.1&package-id=6cc16bbb16d67380" type="library">
          <publisher>Natanael Copa <ncopa@alpinelinux.org></publisher>
          <name>alpine-baselayout</name>
          <version>3.4.3-r2</version>
          <description>Alpine base dir structure and init scripts</description>
          <licenses>
            <license>
              <id>GPL-2.0-only</id>
            </license>
          </licenses>
          <cpe>cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.4.3-r2:*:*:*:*:*:*:*</cpe>
          <purl>pkg:apk/alpine/[email protected]?arch=aarch64&distro=alpine-3.19.1</purl>
          <externalReferences>
            <reference type="distribution">
              <url>https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout</url>
            </reference>
          </externalReferences>
          <properties>
            <property name="syft:package:foundBy">apk-db-cataloger</property>
            <property name="syft:package:type">apk</property>
            <property name="syft:package:metadataType">apk-db-entry</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.4.3-r2:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.4.3-r2:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.4.3-r2:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine:alpine-baselayout:3.4.3-r2:*:*:*:*:*:*:*</property>
            <property name="syft:cpe23">cpe:2.3:a:alpine:alpine_baselayout:3.4.3-r2:*:*:*:*:*:*:*</property>
            <property name="syft:location:0:layerID">sha256:b09314aec293bcd9a8ee5e643539437b3846f9e5e55f79e282e5f67e3026de5e</property>
            <property name="syft:location:0:path">/lib/apk/db/installed</property>
            <property name="syft:metadata:gitCommitOfApkPort">7749273fed55f6e1df7c9ee6a127f18099f98a94</property>
            <property name="syft:metadata:installedSize">331776</property>
            <property name="syft:metadata:originPackage">alpine-baselayout</property>
            <property name="syft:metadata:pullChecksum">Q1ckulh8g/Qo95BDLslgy7tnYfSio=</property>
            <property name="syft:metadata:pullDependencies:0">alpine-baselayout-data=3.4.3-r2</property>
            <property name="syft:metadata:pullDependencies:1">/bin/sh</property>
            <property name="syft:metadata:size">8899</property>
          </properties>
        </component>
        <component bom-ref="os:[email protected]" type="operating-system">
          <name>alpine</name>
          <version>3.19.1</version>
          <description>Alpine Linux v3.19</description>
          <swid name="alpine" tagId="alpine" version="3.19.1"/>
          <externalReferences>
            <reference type="issue-tracker">
              <url>https://gitlab.alpinelinux.org/alpine/aports/-/issues</url>
            </reference>
            <reference type="website">
              <url>https://alpinelinux.org/</url>
            </reference>
          </externalReferences>
          <properties>
            <property name="syft:distro:id">alpine</property>
            <property name="syft:distro:prettyName">Alpine Linux v3.19</property>
            <property name="syft:distro:versionID">3.19.1</property>
          </properties>
        </component>
      </components>
    </bom>

    Again, there is a lot more data than the table allows, but a different set of data than the SPDX format because there simply is not a one-to-one mapping of properties between the two.

    Generating an SBOM in Syft Lossless format

    The last format we’ll discuss is Syft’s JSON format. When using Syft with Grype or if there isn’t a need to provide an SBOM to other tools, the Syft JSON format has the highest fidelity. Both SPDX and CycloneDX lose some information from the initial Syft data model, whereas the Syft format does not.

    Although Grype works great with SPDX and CycloneDX, there could be a situation where data was lost converting to one of these formats. Grype matching uses some of that extra data, so using the Syft JSON might make the most sense. To use the Syft JSON format, use the -o json argument.

    Additional Syft Features

    There’s a lot more that Syft can do, with quite a few configuration options. A few things to note include:

    • Output the SBOM to a file using --file path/to/file
    • Exclude paths from scanning using --exclude path/**/*.txt
    • Specify configuration in a .syft.yaml file
    • Connect to private OCI registries
    • Cryptographically sign and attest SBOMs

    Next Steps

    Now that you’ve got an SBOM, what’s next? A logical next step would be to integrate with your build pipeline to have SBOMs generated automatically. In fact, there could be more than one location where it makes sense to generate SBOMs such as build time and after a container is built or during a release process.

    The SBOMs then could be scanned for license compliance and continuously for vulnerabilities. In fact, if you are using GitHub Actions, there are a couple actions to do just that: sbom-action to generate SBOMs using Syft and scan-action to perform container vulnerability scanning. For a few repositories, it’s very simple to set these up but might be challenging when there are a lot of repositories to keep track of.

    Managing SBOMs at scale

    As we’ve talked about, using SBOMs as a central part of securing your software supply chain is increasingly important. Integrating automated SBOM generation into your DevOps process is vital. Storing, managing, and analyzing those SBOMs to inform security measures should be an important consideration for you and your organization.

    For more comprehensive SBOM management, an enterprise-level solution like Anchore Enterprise will enable you to generate comprehensive SBOMs with every build, detect drift from one build to the next, share SBOMs internally or externally, and quickly identify risks such as vulnerabilities, secrets, malware, and misconfiguration. If you would like to learn more about Anchore Enterprise, schedule a demo with one of our specialists.

    Conclusion

    Now that you understand the many reasons to generate SBOMs (whether for compliance or vulnerability analysis) using Syft to generate SBOMs is a flexible and simple process with many options to tailor SBOMs to your specific use cases.

    If you’d like to explore using Anchore Enterprise for its robust features like continuous visibility, SBOM monitoring, drift detection, and policy enforcement then access a free 15 day trial here.

    Speak with our security experts

    Learn how Anchore’s SBOM-powered platform can help secure your software supply chain.