Bill of Materials CKS Refresher

A Software Bill of Materials (SBOM) is like the ingredients list on your food package—it reveals what components, libraries, and dependencies go into building the final software. Just as checking food labels helps you understand nutritional content and potential allergens, an SBOM provides transparency into third-party components, helping identify vulnerabilities early in the software supply chain. Notably, the Cloud Native Computing Foundation’s Certified Kubernetes Security Specialist certification has updated its objectives to emphasize the critical role of SBOMs in enhancing software security and supply chain integrity. In this blog post I’m examining the use of bom and trivy to demonstrate how you can generate a SBOM for your container images but also scan for vulnerabilities.

Getting started with BOM for Kubernetes

Requirements to replicate this in your environment are the following items I’m using for this demo.

  • Kubernetes Cluster (I’m running v1.31.0)
  • BOM (Sig) Installed
  • Trivy
  • Kubectl

For starting off we are going to use the Bom package by the Kubernetes Sig Repository linked here

curl -L  https://github.com/kubernetes-sigs/bom/releases/download/v0.6.0/bom-amd64-linux -o bom
sudo mv ./bom /usr/local/bin/bom
sudo chmod +x /usr/local/bin/bom

Now once we run this on our local machine and run “bom” we should see the following.

Next we are installing Trivy by Aqua Security this is a OSS project that allows for container scanning but also use of SBOM.

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.59.0

Let’s run a quick command to see our images in our kube-system this we can use to generate a SBOM.

kubectl get pods -n kube-system -o yaml | grep image:

This should return all the pods that are running under our namespace kube-system related container images in the pods.

We can now use this syntax to feed our command to generate a Bill of Materials.

mkdir cks
cd cks
touch sbom1.json
bom generate --image=registry.k8s.io/kube-scheduler:v1.31.0 --format json --output /root/cks/sbom1.json

This should produce the output as shown similar.

If we run the following command to view this we can see the output did convert our format request to json.

We can now leverage Trivy to run our SBOM through and check for vulnerabilities in the command line.

Since Trivy takes SBOMs as target it requires the following file formats (.spdx, .spdx.json, .cdx, .cdx.json)

For some odd reason it gave me some errors on analyzing the .spdx, so I went with the following.

trivy image --format spdx --output result.spdx registry.k8s.io/kube-scheduler:v1.31.0

trivy sbom ./result.spdx

So what did we just complete? We created initially a inventory using the Bill of Materials of the registered image on our kube-system then analyzed the components for vulnerabilities so we have a clear picture of what is affected.

Lets take a peak of the .spdx file produced as a example and we can compare what looks different in the cyclonedx format.

{
      "name": "k8s.io/utils",
      "SPDXID": "SPDXRef-Package-2454e40bf93d3cbe",
      "versionInfo": "v0.0.0-20240711033017-18e509b52bc8",
      "supplier": "NOASSERTION",
      "downloadLocation": "NONE",
      "filesAnalyzed": false,
      "sourceInfo": "package found in: usr/local/bin/kube-scheduler",
      "licenseConcluded": "NOASSERTION",
      "licenseDeclared": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:golang/k8s.io/utils@v0.0.0-20240711033017-18e509b52bc8"
        }
      ],
      "primaryPackagePurpose": "LIBRARY",
      "annotations": [
        {
          "annotator": "Tool: trivy-0.59.0",
          "annotationDate": "2025-02-01T19:43:06Z",
          "annotationType": "OTHER",
          "comment": "LayerDiffID: sha256:f25034a5f5bc2aa0ff067557ad89f14aa45065ad69276bea06fa51c2accf1f0f"
        },
        {
          "annotator": "Tool: trivy-0.59.0",
          "annotationDate": "2025-02-01T19:43:06Z",
          "annotationType": "OTHER",
          "comment": "LayerDigest: sha256:52e1437459d6fe154a2246769da248be5dba1e02254e812188e2f9d1aa6b38a5"
        },
        {
          "annotator": "Tool: trivy-0.59.0",
          "annotationDate": "2025-02-01T19:43:06Z",
          "annotationType": "OTHER",
          "comment": "PkgID: k8s.io/utils@v0.0.0-20240711033017-18e509b52bc8"
        },
        {
          "annotator": "Tool: trivy-0.59.0",
          "annotationDate": "2025-02-01T19:43:06Z",
          "annotationType": "OTHER",
          "comment": "PkgType: gobinary"
        }
      ]
    },

We run the following to regenerate in the CycloneDX format to show the difference.

trivy image --format cyclonedx --output result.cdx registry.k8s.io/kube-scheduler:v1.31.0

Now we we run a cat command to review the file we can see the difference but to put this in context I’ll put in a snippet.

 {
      "bom-ref": "pkg:golang/sigs.k8s.io/yaml@v1.4.0",
      "type": "library",
      "name": "sigs.k8s.io/yaml",
      "version": "v1.4.0",
      "purl": "pkg:golang/sigs.k8s.io/yaml@v1.4.0",
      "properties": [
        {
          "name": "aquasecurity:trivy:LayerDiffID",
          "value": "sha256:f25034a5f5bc2aa0ff067557ad89f14aa45065ad69276bea06fa51c2accf1f0f"
        },
        {
          "name": "aquasecurity:trivy:LayerDigest",
          "value": "sha256:52e1437459d6fe154a2246769da248be5dba1e02254e812188e2f9d1aa6b38a5"
        },
        {
          "name": "aquasecurity:trivy:PkgID",
          "value": "sigs.k8s.io/yaml@v1.4.0"
        },
        {
          "name": "aquasecurity:trivy:PkgType",
          "value": "gobinary"
        }
      ]
    }
  ],
  "dependencies": [
    {
      "ref": "67eae981-a816-4946-9869-1e41ad8aba27",
      "dependsOn": [
        "pkg:golang/k8s.io/kubernetes"
      ]
    },
    {
      "ref": "936a7ffe-f36e-4dc8-973e-7e19714499e5",
      "dependsOn": []
    },

Notice instead of the license annotations we saw in the first format of SPDX we see visibility on the packages that are installed and what they depend on with the annotation of libraries.

Now wouldn’t it be great if we can run a SBOM and a vulnerability scan of the packages?

trivy image --format cyclonedx --output result.cdx registry.k8s.io/kube-scheduler:v1.31.0 --scanners vuln

On our result.cdx we will see the vulnerabilities have been mapped to our format along with source, severity and other details.

Back to our BOM tool we can use the –analyze-images flag that will perform a deeper scan of the image the output will look like the following.

 {
      "spdxElementId": "SPDXRef-Package-registry.k8s.io-kube-schedulerC64sha256-96ddae9c9b2e79342e0551e2d2ec422c0c02629a74d928924aaa069706619808-registry.k8s.io-kube-schedulerC64sha256-802a6ba26701149077241a44424b49ddf8fc92a3fa11b515e23335f5a8a2577c",
      "relationshipType": "VARIANT_OF",
      "relatedSpdxElement": "SPDXRef-Package-sha256-96ddae9c9b2e79342e0551e2d2ec422c0c02629a74d928924aaa069706619808"
    },

In summary we can use both tools interchangeably the idea is transparency of our supply chain at the heart of the cluster is the pods which are the smallest objects in our cluster but are also running software as containers. The visibility of what is running and how it can affect our vulnerability management

Summary

When using a hosted platform—even trusted PaaS solutions like EKS, GKE, or AKS—it’s essential not to assume that your cloud service provider has addressed every upstream vulnerability. This blog demonstrates the following ways you can run a Bill of Materials generation whether this is in the SPDX format or CycloneDX. Additionally if you want to scan the SBOM for vulnerabilities its helpful to have a container image scanning that will be able to map your SBOM to vulnerabilities. Ideally you’d have the following areas integrated in a CI workflow to ensure prior to release these are addressed and mitigated from vulnerabilities prior to releasing.