How To Build an SBOM

A Software Bill of Materials (SBOM) is getting more and more important in the software supply chain. In this blog, you will learn what an SBOM is and how to build the SBOM in an automated way. Enjoy!

1. Introduction

An SBOM is a list of software components that makes up a software product. This way, it becomes transparent which software libraries, components, etc., and their versions are used in the software product. As a consequence, you will be able to react more adequately when a security vulnerability is reported. You only need to check the SBOMs for the vulnerable library, and you will know immediately which applications are impacted by the vulnerability. The SBOM of a library or application you want to use can also help you in your decision-making. It will become more common ground that software suppliers will be forced to deliver an up-to-date SBOM with each software delivery. Based on this information, you can make a risk assessment of whether you want to use the library or application.

When you are a software supplier, you need to ensure that you deliver an SBOM with each software release. This actually means that you need to create the SBOM in an automated way, preferably in your build pipeline. As written before, the SBOM can help you to check whether a library used in your application contains security vulnerabilities. With the proper tooling, this check can be done in an automated way in your build pipeline. When security vulnerabilities are found, you can fail the build pipeline. One of these tools is grype, which can take an SBOM as input and check whether any components are used with known security vulnerabilities. In a previous post, it is explained how grype can be used. In the post, a Docker image is input to grype, but it is even better to create an SBOM first and then provide it to grype. How to create the SBOM will be explained in this post.

When you start reading about SBOMs, you will notice that two standards are commonly used:

  • CycloneDX: an open-source project originated within the OWASP community;
  • SPDX (The Software Package Data Exchange): an international open standard format, also open source and hosted by the Linux foundation.

So, which standard to use? This is a difficult question to answer. Generally, it is stated that both standards will continue to exist next to each other, and tools are advised to support both standards. SPDX is initially set up for license management, whereas CycloneDX had its primary focus on security. Reading several resources, the preferred format is CycloneDX when your focus is set on security. Interesting reads are SBOM formats SPDX and CycloneDX compared and the publication Using the Software Bill of Materials for Enhancing Cybersecurity from the National Cyber Security Centre of the Ministry of Justice and Security of the Netherlands. The latter is a must-read.

In the remainder of this blog, you will learn how to use syft for building an SBOM. Syft is also a product of Anchore, just like grype is, and therefore integrates well with grype, the vulnerability scanning tool. Syft supports many ecosystems and has several export formats. It definitely supports CycloneDX and SPDX. CycloneDX also has tools for building SBOMs, but Syft is one tool that supports many ecosystems, which is an advantage compared to multiple tools.

Sources being used in this post are available at GitHub.

2. Prerequisites

The prerequisites needed for this blog are:

  • Basic Linux knowledge;
  • Basic Java knowledge;
  • Basic JavaScript knowledge;
  • Basic Spring Boot knowledge.

3. Application Under Test

Before continuing, you need an application to build the SBOM. This is a basic application that consists of a Spring Boot backend and a Vue.js frontend. The application can be built with Maven and contains two Maven modules, one for the backend and one for the front end. More information about the setup can be read in a previous post. It is important, however, to build the application first. This can be done with the following command:

4. Installation

Installation of syft can be done by executing the following script:

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

Verify the installation by executing the following command:

$ syft --version
syft 0.64.0

5. Build Backend SBOM

Navigate to the backend directory and execute the following command:

$ syft dir:. --exclude ./**/sbom.*.json --output cyclonedx-json=sbom.cyclonedx.build-complete-dir.json

The parameters will do the following:

  • dir:.: Scan the entire directory in order to find dependencies;
  • –exclude: Exclude already present SBOM files because you want to generate the SBOM file every time anew based on the current state of the repository;
  • –output: Here, you define the output format to use, and you define the file name of the SBOM file.

The SBOM file sbom.cyclonedx.build-complete-dir.json is created in the backend directory. Take a closer look at the SBOM format.

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "serialNumber": "urn:uuid:afbe7b48-b376-40fb-a0d4-6a16fda38a0f",
  "version": 1,
  "metadata": {
    "timestamp": "2023-01-14T16:35:35+01:00",
    "tools": [
      {
        "vendor": "anchore",
        "name": "syft",
        "version": "0.64.0"
      }
    ],
    "component": {
      "bom-ref": "af63bd4c8601b7f1",
      "type": "file",
      "name": "."
    }
  },
  "components": [
   ...
 ]
}

The top part consists of metadata: the format of the SBOM, versions used, which tool is being used, etc. The component part consists of a list of all the components syft has found. The complete specification of CycloneDX can be found here.

The component list is the following and corresponds to the list of libraries that can be found in the target/backend-0.0.1-SNAPSHOT.jar file. The libraries are located in the directory /BOOT-INF/lib/ in the jar file (the jar file is just a zip file and can be opened with any archive tool).

backend
jackson-annotations
jackson-core
jackson-databind
jackson-datatype-jdk8
jackson-datatype-jsr310
jackson-module-parameter-names
jakarta.annotation-api
jul-to-slf4j
log4j-api
log4j-to-slf4j
logback-classic
logback-core
micrometer-commons
micrometer-observation
slf4j-api
snakeyaml
spring-aop
spring-beans
spring-boot
spring-boot-autoconfigure
spring-boot-jarmode-layertools
spring-boot-starter-test
spring-boot-starter-web
spring-context
spring-core
spring-expression
spring-jcl
spring-web
spring-webmvc
tomcat-embed-core
tomcat-embed-el
tomcat-embed-websocket

Now take a closer look at the jackson-annotations component in the SBOM file. In the properties section, you can see that this component has a property syft:package:foundBy with the value java-cataloger. This means that this component was found in the jar file.

{
      "bom-ref": "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.14.1?package-id=9cdc3a1e17ebbb68",
      "type": "library",
      "group": "com.fasterxml.jackson.core",
      "name": "jackson-annotations",
      "version": "2.14.1",
      "cpe": "cpe:2.3:a:jackson-annotations:jackson-annotations:2.14.1:*:*:*:*:*:*:*",
      "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.14.1",
      "externalReferences": [
        {
          "url": "",
          "hashes": [
            {
              "alg": "SHA-1",
              "content": "2a6ad504d591a7903ffdec76b5b7252819a2d162"
            }
          ],
          "type": "build-meta"
        }
      ],
      "properties": [
        {
          "name": "syft:package:foundBy",
          "value": "java-cataloger"
        },
        {
          "name": "syft:package:language",
          "value": "java"
        },
        {
          "name": "syft:package:metadataType",
          "value": "JavaMetadata"
        },
        {
          "name": "syft:package:type",
          "value": "java-archive"
        },
        ...
       ]
}

When you take a look at component spring-boot-starter-web, it mentions that this component was found by java-pom-cataloger. This means that this component was found in the pom file.

This is quite interesting because this would mean that syft cannot find transitive dependencies based on the sources only. Execute the following command where the target directory is excluded from the analysis.

$ syft dir:. --exclude ./**/sbom.*.json --exclude ./**/target --output cyclonedx-json=sbom.cyclonedx.build-sources.json

The result can be found in the file sbom.cyclonedx.build-sources.json and the previously made assumption seems to be right. Only the spring-boot-starter-web and spring-boot-starter-test dependencies are found. This is, after all, not a big issue, but you have to be aware of this.

6. Build Frontend SBOM

Navigate to the frontend directory and execute the following command:

$ syft dir:. --exclude ./**/sbom.*.json --output cyclonedx-json=sbom.cyclonedx.build-complete-dir.json

This analysis takes a bit longer than the backend analysis, but after a few seconds, the sbom.cyclonedx.build-complete-dir.json file is created. Again, similar information can be found in the SBOM. The information is now available from the javascript-lock-cataloger. This means that it originates from the package-lock.json file.

Another difference is that the components also contain license information.

"components": [
    {
      "bom-ref": "pkg:npm/%40babel/parser@7.20.7?package-id=ca6a526d8a318088",
      "type": "library",
      "name": "@babel/parser",
      "version": "7.20.7",
      "licenses": [
        {
          "license": {
            "id": "MIT"
          }
        }
      ],
      ...

License information is included and can be used to check regarding allowed company policies. This information is, however, not yet available for Java packages.

7. Conclusion

SBOMs will become more and more important in the software development lifecycle. More and more customers will demand an SBOM, and therefore it is important to automatically generate the SBOM. Syft can help you with that. Besides that, the SBOM can be fed to grype in order to perform a security vulnerability analysis.


Source link