Software Bill of Materials (SBOM) provide transparency that is an important building block for identifying risk in the software supply chain. An SBOM is a list of all the components in a piece of software, including their dependencies, and relationships between them. It optionally also contains the licenses that govern those components. An accurate and up-to-date SBOM enables security teams to detect outdated or vulnerable components, assists compliance with identifying unacceptable licensing, and overall helps organizations to gain visibility into what they consume, as well as how and what they ship.
SBOMs are becoming increasingly more prominent in software requirements, with many governments and organizations mandating their use, and providing relevant guidelines. As a response there is a growing number of enterprise and open source tools that allow generation and management of SBOMs such as Trivy, OWASP Dependency-Track, Syft, Tern, Mend (formerly Whitesource), FOSSA and many others.
In Smithy we need to monitor our software for outdated libraries, vulnerable components and unacceptable licenses. We also need this information readily available, updated automatically and of course easy to digest. Luckily we have Smithy, a very powerful security workflow engine which allows us to seamlessly unify any security tooling. In this post we demonstrate how Smithy makes creation and maintenance of high quality SBOMs trivial for any organization size.
Use Case
A real-world scenario would be the following: A large organization with many interdependent projects needs to generate SBOMs for all of its projects. The SBOMs need to be accurate and high quality. Quality needs to be measurable and the SBOMs need to be stored and visualized in an external system. We can accurately simulate such a scenario by generating SBOMs for a large Github organization with many repositories in multiple states of maintenance but also high security requirements. The Kubernetes Github organization is acceptable for this.
Tooling
There are many tools that can generate SBOMs. As an example, Trivy supports the creation of SBOMs from a filesystem and can produce output in the popular SBOM format CycloneDX. OWASP Dependency-Track can ingest CycloneDX documents, and store and visualize the information within them for easy observability and management. As a convenient addition, it also monitors vulnerability databases (e.g. NVD and OSV), and notifies us in case it identified vulnerable components in our portfolio.
Smithy as a security workflow management solution can orchestrate all these tools and its API supports CycloneDX information.
Quality
Ebay’s sbom-scorecard checks SBOMs for completeness and provides a numerical score. We will use it as a way to determine if we did a good job.
Execution
First, we need to know the baseline SBOM score we can get from different tools.
We clone a random repository from our Github organization, (kubernetes/kops) and run Trivy against it.
docker run -v $(pwd):/code -ti aquasec/Trivy filesystem -f cyclonedx /code >sbom.json
This produces an sbom file which can then be read by our scorecard solution as such.
$ docker run -v $(pwd):/code /sbom-scorecard score /code/sbom.json
Guessed: cdx
424 total packages
99% have versions.
0% have licenses.
0% have package digest.
99% have purls.
0% have CPEs.
Has creation info? true
Spec valid? true
==
Spec Compliance: 25/25
Package ID: 9/20 (99% have purls and 0% have CPEs)
Package Versions: 19/20
Package Licenses: 0/20
Creation Info: 15/15
Total points: 69/100 or 69%
The score is 69% which is acceptable, we have package information and package URLs, but we lack licensing, digests and CPEs. The information we get from Trivy will serve our purpose of basic vulnerability analysis and package evaluation use cases. Similarly, running Syft against the same repository provides us with a valid SBOM document, which however is sadly unrecognizable by sbom-scorecard.
Guessed: spdx
0 total packages
0 total files
0% have licenses.
0% have package digest.
0% have package versions.
0% have purls.
0% have CPEs.
0% have file digest.
Spec valid? true
Has creation info? false
==
Spec Compliance: 25/25
Package ID: 0/20 (No packages)
Package Versions: 0/20 (No packages)
Package Licenses: 0/20 (No packages)
Creation Info: 0/15 (No creation info found)
Total points: 25/100 or 25%
Therefore in this particular case we will proceed using Trivy.
Having selected our tooling, we can then upload this information to Dependency Track via it’s GUI. First we create a project
Then we select our project, click on the components tab and click on ‘Upload BOM’
After Dependency Track has processed our BOM we get more information such as potential vulnerabilities and a risk score but also we can visualize all of the components of the repository. We can also download our enriched BOM and see our new score.
$ docker run -v $(pwd):/code /sbom-scorecard score ./kops-bom-dt.json
Guessed: cdx
424 total packages
99% have versions.
0% have licenses.
0% have package digest.
99% have purls.
0% have CPEs.
Has creation info? true
Spec valid? true
==
Spec Compliance: 25/25
Package ID: 9/20 (99% have purls and 0% have CPEs)
Package Versions: 19/20
Package Licenses: 0/20
Creation Info: 15/15
Total points: 69/100 or 69%
No change. This is because sbom-scorecard
does not provide a score for vulnerability information. Being able to communicate vulnerability information by embedding it into the SBOM is useful, but in this case it doesn’t help us in increasing our score.
Let’s see if we can raise the score before we submit the SBOM to Dependency-Track, to provide it with even more and better data to perform analyses on!
An easy win which also provides immediate value to us is adding Licensing information to our BOM.
We can do so by using Smithy’s deps.dev enricher which queries Google’s OpenSource insights dataset for licensing information on a package.
We can then visualize our results using Smithy’s Dependency Track Consumer.
To do so, we need a Pipeline that runs Trivy, enriches its results with information from OpenSource insights and finally puts the results into Dependency Track:
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
nameSuffix: -sbom-project
namespace: smithy
resources:
- https://github.com/smithy-security/smithy//components/base/
components:
- https://github.com/smithy-security/smithy//components/sources/git/
- https://github.com/smithy-security/smithy//components/producers/aggregator/
- https://github.com/smithy-security/smithy//components/producers/docker-Trivy
- https://github.com/smithy-security/smithy//components/enrichers/aggregator/
- https://github.com/smithy-security/smithy//components/enrichers/depsdev/
- https://github.com/smithy-security/smithy//components/consumers/dependency-track
We can install this pipeline with the following command:
kubectl -n smithy apply -k .
We also need to configure these components, we can do so with a pipelinerun.yaml
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: smithy-sbom-project-
namespace: smithy
spec:
pipelineRef:
name: smithy-sbom-project
params:
- name: repository_url
value: $repo-url
- name: consumer-dependency-track-api-url
value: http://localhost:7070
- name: consumer-dependency-track-project-name
value: "$project-name"
- name: consumer-dependency-track-project-uuid
value: "$project-uuid"
- name: consumer-dependency-track-project-version
value: "v0.0.1"
- name: consumer-dependency-track-token
value: "$dt-token"
- name: producer-docker-Trivy-format
value: "cyclonedx"
- name: producer-docker-Trivy-command
value: "fs"
workspaces:
- name: source-code-ws
subPath: source-code
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
We can start this pipeline run with the following command:
kubectl -n smithy create -f pipelinerun.yaml
Running this uploads licensing information to Dependency Track with no extra effort on our side, it is also automated and easily customized, not a bad start. Our new score is 89%:
$ docker run -v $(pwd):/code /sbom-scorecard score /code/sbom-with-licenses.json
Guessed: cdx
424 total packages
99% have versions.
98% have licenses.
0% have package digest.
99% have purls.
0% have CPEs.
Has creation info? true
Spec valid? true
==
Spec Compliance: 25/25
Package ID: 9/20 (99% have purls and 0% have CPEs)
Package Versions: 19/20
Package Licenses: 19/20
Creation Info: 15/15
Total points: 89/100 or 89%
The simple Smithy pipeline we assembled throughout the course of this post is a great example of how multiple tools can collaborate in delivering high quality SBOMs to upstream Consumers like Dependency-Track. We achieved this by writing barely any code, in a declarative way that is easy to understand and maintain.
Orchestration
Manually running pipelines for all of our organization however is inefficient. We can easily scale this process with the following steps:
- Use Dependency Track’s API to create a project for each of our repositories.
- Write a pipeline-run template which has variables for each parameter we need to change per repository.
- For every repository in our organization, create a templated
pipelinerun.yaml
. - Mass-execute all our pipeline runs.
- Profit
We can mass-create Dependency Track projects using the script found here Once the required dependencies have been installed, we can execute the script as such:
DT_API_KEY=<> DT_URL=<> ./create-projects.sh
This gives us a mapping of Repository Name to Dependency Track Project UUID which we can save in a file named results.txt
.
We can now mass-generate templated pipelinerun.yaml
files with the script found here
go run main.go -apiKey <dependency-track-api-key> -output $(pwd)/pipelineruns -resultsFile $(pwd)/result.txt -template pipelinerun.yaml.tmpl -url <dependency-track-url>
This creates several .yaml
files in the directory pipelineruns
.
We can now install and run our pipelines with
kubectl -n smithy apply -k .
and run them with:
for run in $(ls pipelineruns/); do k -n smithy create -f pipelineruns/$run; done
When the runs finish, we can see the results in Dependency Track for all our repositories. Repeat executions can be achieved via cronjob. The whole pipeline along with detailed instructions on recreating this for ANY github organization is in the supporting material of this post in our community-pipelines repository here
Improvements
A cronjob can fail for many reasons, moreover, cronjobs offer a point in time execution while SBOMs change possibly with every commit, in Smithy, we prefer a more resilient approach. Therefore in Smithy enterprise we use its Github Application integration to schedule the BOM pipeline to run on every single commit for every repository. This is transparent to colleagues using Github and allows our security team immediate visibility into our current SBOMs while also allowing us to get notified automatically when a component has a CVE by subscribing to Dependency Track notifications. This is just one example of the great workflows we are performing through Smithy, contact us to find out more.