Skip to content

Commit

Permalink
Overhaul "How to Sign an SBOM with Cosign" article (#1844)
Browse files Browse the repository at this point in the history
## Type of change

Overhaul the tutorial on signing an SBOM with Cosign.

Resolves chainguard-dev/internal#4273

---------

Signed-off-by: Patrick Smyth <patrick.smyth@chainguard.dev>
Co-authored-by: Erika Heidi <erikaheidi@users.noreply.github.com>
  • Loading branch information
smythp and erikaheidi authored Oct 10, 2024
1 parent bec1938 commit 9c1caf6
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 77 deletions.
281 changes: 204 additions & 77 deletions content/open-source/sigstore/cosign/how-to-sign-an-sbom-with-cosign.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
title: "How to Sign an SBOM with Cosign"
type: "article"
description: "Signing software bills of materials with Cosign"
lead: "Use Cosign to sign software bills of materials"
lead: "Use Cosign to sign software bills of materials (SBOMs)"
date: 2022-13-07T15:22:20+01:00
lastmod: 2024-07-29T15:12:18+00:00
lastmod: 2024-10-10T15:12:18+00:00
draft: false
tags: ["Cosign", "Procedural", "SBOM"]
images: []
Expand All @@ -17,143 +17,270 @@ toc: true

_An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._

Cosign is a useful tool for signing software artifacts. Signatures are a form of metadata, and you can add other signed metadata to make different assertions about a software package.
[Cosign](https://github.com/sigstore/cosign), developed as part of the [Sigstore project](https://www.sigstore.dev/), is a command line utility for signing, verifying, storing, and retrieving software artifacts through interface with an OCI (Open Container Initiative) registry. Cosign can be used to sign attestations, or a verifiable assertion or statement about a software artifact.

For example, a [software bill of materials](https://www.cisa.gov/sbom), or **SBOM**, is an inventory of the components that make up a given software artifact. Increasingly, SBOMs are considered part of the foundation that makes a more secure software supply chain.
{{< details "What is an Attestation?" >}}
{{< blurb/attestation >}}
{{< /details >}}

As a developer leveraging the software that others make, an SBOM can help you understand what goes into the software that you’re using. As a developer releasing software into the world, including an SBOM with what you ship can help others trust the provenance of the software. You can instill greater trust in your software products by signing your SBOMs along with other software artifacts.
One common use case for attestations is associating a software artifact, such as an OCI container image, with a [software bill of materials (SBOM)](/open-source/sbom/what-is-an-sbom/), an inventory of the components that make up a given software artifact. Increasingly, SBOMs are considered an essential component in maintaining a secure software supply chain.

Let’s demonstrate how to create an SBOM and sign the SBOM using the `hello-container` example from the [How to Sign a Container with Cosign](/open-source/sigstore/cosign/how-to-sign-a-container-with-cosign/) tutorial. Alternatively, you can use another container that you have on hand.
{{< details "What is a Software Bill of Materials (SBOM?" >}}
{{< blurb/sbom >}}
{{< /details >}}

## Install Syft
Including an SBOM with software you ship can help others trust the provenance and contents of the software. Including your SBOM as an attestation verifies that the SBOM was generated by the same trusted organization that created the software artifact, and that neither the artifact nor its associated metadata and documents have been tampered with.

We can create an SBOM with the open source Syft tool from the Anchore community. First, to install Syft, you can review the guidance on installation on the project’s [README file](https://github.com/anchore/syft#installation). Please note that Syft’s current recommended installation is to use `curl` to download a script that is hosted in their GitHub repository. We recommend that you inspect the script to make sure you know what you’re executing.
In the following, we'll generate an SBOM and associate it with a specific OCI container using an attestation generated by Cosign.

You can alternatively use Homebrew for macOS or Linux as a package manager to install Syft:
## Creating a Demonstration Image

Since we'll be attaching an SBOM to a container image, we'll first need to create an example image. We'll base this image on Chainguard's [`wolfi-base`](https://edu.chainguard.dev/open-source/wolfi/overview), and add a single additional package, the venerable `cowsay` utility that prints a message along with some ASCII art. We then set the entrypoint so that, when the image is run, a message will be displayed.

Create a new folder for our Dockerfile build and change your working directory to that folder:

```sh
brew tap anchore/syft
brew install syft
mkdir -p ~/example-image && cd ~/example-image
```

If you will use `curl` to install Syft, we recommend inspecting the [install.sh](https://github.com/anchore/syft/blob/main/install.sh) file prior to downloading. Before piping the command through, it is always a good idea to audit the script. Let’s first download the file.
Create a new `Dockerfile` in the folder using Nano or your preferred text editor:

```sh
curl -O https://raw.githubusercontent.com/anchore/syft/main/install.sh
nano Dockerfile
```
Paste the following commands into the file:

```Dockerfile
FROM cgr.dev/chainguard/wolfi-base
RUN apk add cowsay
ENTRYPOINT ["cowsay", "-f", "tux", "I love FOSS!"]
```

You can now open the file and review it in a text editor such as `nano` or `vi`. Once you are satisfied with your audit of the file, and if you are happy to move forward, you can now run the install script.
Save the file and close Nano by pressing `CTRL + X`, `y`, and `ENTER` in succession.

Since we'll be pushing to a repository on your Docker Hub account, let's set a variable to your Docker Hub username that we can use in further commands.

```sh
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
DH_USERNAME=<your-username>
```

With Syft installed, you can generate an SBOM with the `syft` command.
You can find your username on your Docker Hub account page. If you're logged in on the command line, you can also run the following command to find your username:

## Generate an SBOM
```sh
docker info | sed '/Username:/!d;s/.* //' 2> /dev/null
```

Using the Syft tool, you can begin to generate an SBOM by using the `syft` command and calling your container image. In our example, the container is your Docker username and the `hello-container` image.
Finally, let's build and tag the image with:

```sh
syft docker-username/hello-container:latest
docker build . -t $DH_USERNAME/example-image
```

You should receive output regarding all the components in your container. If you created the same container that we demonstrated in our tutorial, your output should be very similar to the below.
You can test out the image by running it:

```sh
docker run $DH_USERNAME/example-image
```
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [14 packages]

NAME VERSION TYPE
alpine-baselayout 3.2.0-r18 apk
alpine-keys 2.4-r1 apk
apk-tools 2.12.7-r3 apk
busybox 1.34.1-r5 apk
ca-certificates-bundle 20211220-r0 apk
libc-utils 0.7.2-r3 apk
libcrypto1.1 1.1.1n-r0 apk
libretls 3.3.4-r3 apk
libssl1.1 1.1.1n-r0 apk
musl 1.2.2-r7 apk
musl-utils 1.2.2-r7 apk
scanelf 1.3.3-r0 apk
ssl_client 1.34.1-r5 apk
zlib 1.2.12-r0 apk
This should display the "I love FOSS!" message along with some ASCII art.

## Generating an SBOM with Syft

[Syft](https://github.com/anchore/syft) is a tool that allows us to create SBOMs. If you don't already have Syft, use the following instructions to install this utility.

{{< details "How to Install Syft" >}}
{{< blurb/install_syft >}}
{{< /details >}}

Once you have Syft installed, you can generate an SBOM with the `syft` command, passing in the target image as an argument. For example, the following will use Syft to generate a list of packages present in the official `python` image on Docker Hub:

```sh
syft python
```

We would like this SBOM to be output to a particular file format that we can sign with Cosign. We’ll use the Linux Foundation Project **[SPDX](https://spdx.dev/)** format, which stands for Software Package Data Exchange. SPDX is an open standard for communicating SBOM information.
Now let's generate an SBOM for `example-image`, the image we built in the previous section. Run the following Syft command to generate an SBOM in SPDX format:

```sh
syft $DH_USERNAME/example-image:latest -o spdx-json > example-image.spdx.json
```
{{< details "What is SPDX?" >}}
{{< blurb/spdx >}}
{{< /details >}}

We’ll output this to a file called `latest.spdx` to represent the most recent container version’s SBOM. You may want to version SBOMs along with your releases, but keeping a most up-to-date “latest” version can generally be helpful.
If you take a look at the contents of the file, you will find our installed package, `cowsay`, represented alongside the other packages already in our base image, `wolfi-base`. You can also check that `cowsay` is detected by Syft with the following:

```sh
syft docker-username/hello-container:latest -o spdx > latest.spdx
syft --quiet $DH_USERNAME/example-image:latest | grep cowsay
```

You’ll get output similar to the SBOM output again (without the list of all the components).
```
cowsay 3.04-r0 apk
```

In the next section, we'll associate this SBOM with our container image using Cosign.

## Attesting to the SBOM

Now that we have our SBOM file, let's associate it with our image using an attestation. In this attestation, the image will be our subject, and the generated SBOM will serve as our predicate (an assertion about the subject). In this case, we will be attesting that the image contains the packages listed in our SBOM file.

Before proceeding, let's push our image to Docker Hub, since the following commands will refer to the image on the OCI repository.

```sh
docker push $DH_USERNAME/example-image
```
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [14 packages]

Our `example-image` still has attestations derived from our base image, since all Chainguard Images come with SBOM and SLSA provenance attestations. Let's remove these attestations with the `cosign clean` command:

```sh
cosign clean $DH_USERNAME/example-image
```

With the file written, you can inspect it.
We're now ready to add our attestation. Sigstore recommends referring to the image by digest and not by tag to avoid attesting to the wrong image, and attesting by tag will be removed in a future version of Cosign. The following command will set a variable to the digest of our newly pushed image:

```sh
cat latest.spdx
DIGEST=$(docker inspect $DH_USERNAME/example-image |jq -c 'first'| jq .RepoDigests | jq -c 'first' | tr -d '"')
```

Alternatively, you can find the digest manually by visiting your repository on Docker Hub.

We can now attest using our image as the subject and our generated SBOM as the predicate:

```sh
cosign attest --type spdxjson \
--predicate example-image.spdx.json \
$DIGEST
```

You will receive the following prompt:

```
Generating ephemeral keys...
Retrieving signed certificate...
This will be a fairly lengthy file, even for our small container image. It will provide information for each of the components that make up the software in the example `hello-container` image.
The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
This may include the email address associated with the account with which you authenticate your contractual Agreement.
This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.
By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N]
```
SPDXVersion: SPDX-2.2
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: docker-username/hello-container-latest
##### Package: zlib

PackageName: zlib
SPDXID: SPDXRef-Package-apk-zlib-7934e949300925b1
PackageVersion: 1.2.12-r0
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageLicenseConcluded: Zlib
PackageLicenseDeclared: Zlib
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:a:zlib:zlib:1.2.12-r0:*:*:*:*:*:*:*
ExternalRef: PACKAGE_MANAGER purl pkg:alpine/zlib@1.2.12-r0?arch=aarch64&upstream=zlib&distro=alpine-3.15.4
Note the warnings — a record of the attestation will be recorded to an immutable log maintained by the Sigstore project. When you're ready, press `y` to agree and attest.

## Retrieving the Signed SBOM

Our `example-image` in our Docker Hub repository should now bear an attached SBOM as an attestation. Let's confirm that this is the case by accessing the image's SBOM from the repository using the `cosign download attestation` command:

```sh
cosign download attestation \
$DIGEST | \
jq -r .payload | base64 -d \
| jq .predicate
```

Next, you’ll use Cosign to work with the SBOM and the image.
This command will download the `in-toto attestation` envelope using the `cosign download attestation` command. This envelope contains information on the attestation's subject (the image) and predicate (a statement about the subject, in this case the generated SBOM). We decode this envelope from base64, an encoding used to reduce information loss while transferring data, then extract the predicate. The result should be our SBOM in SPDX-JSON:

```json
{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2024-10-09T19:16:20Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-1.14.0"
],
"licenseListVersion": "3.25"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/$DH_USERNAME/example-image-15096a2c-e277-40f6-bc6e-93c5a4aff24e",
"files": [
{
"SPDXID": "SPDXRef-File-bin-busybox-b93a85ec4cd132fa",
"checksums": [
[...]
```

## Sign the SBOM
If we choose, we can further parse this output so we can examine the `cowsay` package we added in our `Dockerfile`:

```sh
cosign download attestation \
$DIGEST | \
jq -r .payload | base64 -d |\
jq .predicate | jq .packages | \
jq '.[] | select(.name == "cowsay")'
```

```json
{
"SPDXID": "SPDXRef-Package-apk-cowsay-f6f3edc220b6705a",
"copyrightText": "NOASSERTION",
"description": "Configurable talking cow (and a few other creatures)",
"downloadLocation": "NONE",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:cowsay:cowsay:3.04-r0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:apk/wolfi/cowsay@3.04-r0?arch=x86_64&distro=wolfi-20230201",
"referenceType": "purl"
}
],
"filesAnalyzed": true,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "GPL-2.0-or-later",
"name": "cowsay",
"packageVerificationCode": {
"packageVerificationCodeValue": "4f82bdba8e1217f8af0abee5cadc9c2387bf4720"
},
"sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
"supplier": "NOASSERTION",
"versionInfo": "3.04-r0"
}
```

You will sign the SBOM in a similar way to signing other software artifacts.
At this point, we've attested to the contents of our image with an SBOM and confirmed that the attestation is attached to the image in our Docker Hub repository. In the next section, we'll learn how to verify the identity of the entity issuing an attestation.

Make sure you are in the correct local directory for your Cosign key pair. If you generated the key pair in the signed container example, it will be in your home user directory, so make sure you move your present working directory there with the `cd ~` command.
## Verifying an Attestation

You’ll be signing the SBOM with the SHA that you received in the output from the previous command. This is a long string that starts with `sha256` and ends with `.sbom`. You can verify that this was pushed to the container registry by checking the web user interface of Docker Hub or alternate registry.
Cosign can also be used to verify the identity of the person or entity issuing an attestation. The following assumes you used GitHub to authenticate when attesting in the previous step.

We have an example SBOM SHA below, please use the SHA output you received.
To verify that an attestation was issued by a specific entity, we use the `cosign verify-attestation` command, specifying the email address of the issuer:

```sh
cosign sign --key cosign.key docker-username/hello-container:sha256-690ecfd885f008330a66d08be13dc6c115a439e1cc935c04d181d7116e198f9c.sbom
cosign verify-attestation \
--certificate-oidc-issuer=https://github.com/login/oauth \
--type https://spdx.dev/Document \
--certificate-identity=emailaddress@emailprovider.com \
$DIGEST
```

Again, you’ll be prompted for the password for your Cosign private key. Once you enter the password, you’ll receive output that the signature was pushed to the registry.
If the identity is successfully verified, an initial message similar to the following is printed to `stderr`:

```
Pushing signature to: index.docker.io/docker-username/hello-container
Verification for $DH_USERNAME/example-image@sha256:545a731e803b917daf44e292b03b427427f8090c4e6c4a704e4c18d56c38539f --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
Certificate subject: <you@domain.com>
Certificate issuer URL: https://github.com/login/oauth
```

You can verify the signature on the SBOM as you can with any other signature.
The remainder of the message consists of an `in-toto attestation` envelope encoded in base64. If you wish, you can retrieve the predicate from this response:

```sh
cosign verify --key cosign.pub docker-username/hello-container:sha256-690ecfd885f008330a66d08be13dc6c115a439e1cc935c04d181d7116e198f9c.sbom
cosign verify-attestation \
--certificate-oidc-issuer=https://github.com/login/oauth \
--type https://spdx.dev/Document \
--certificate-identity=emailaddress@emailprovider.com \
$DIGEST | \
jq -r .payload | \
base64 -d | jq .predicate
```

As before, you’ll receive output that the SBOM’s signature is verified and you’ll receive a JSON formatted digest of the information.
At this point, you have successfully created an image, generated an SBOM for that image, associated the SBOM with the image as an attestation, and verified the identity of the issuer of the attestation. Using this workflow, you can attest to the contents of images you create, allowing others to understand the provenance of software you ship and enabling others to verify that software artifacts and associated documents originate from you.

You have now created and signed an SBOM for your container!
Loading

0 comments on commit 9c1caf6

Please sign in to comment.