Secure your software supply chain using Sigstore and GitHub actions
Marco Franssen /
9 min read • 1787 words
With the rise of software supply chain attacks it becomes more important to secure our software supply chains. Many others have been writing about software supply chain attacks already, so I won't repeat that over here in this article. Assuming you found my article, because you want to know how to prevent them.
In this blogpost I want to show you how to secure the software supply chain by applying some SLSA requirements in the GitHub actions workflow. We will utilize Sigstore to sign and attest the images. I will also involve a few other tools to generate build provenance and an SBOM for our released Docker images so we can attest that to the images.
We will cover the following topics:
- Having a bare bones release workflow using GitHub actions
- Adding signatures to our Docker images
- Generating an SBOM for our Docker image
- Attest the SBOM to our Docker image
- Generating build provenance for our Docker image
- Attest the build provenance to our Docker image
:warning: In this example I only cover releasing a Docker image, but we can apply same to other artifact types, using the same toolchain.
Limit GitHub workflow permissions
Before we get started I first want to point out that you can/should limit the permissions a GitHub action workflow has by default. See following screenshot on how to limit that.
Ensure you pick the Read repository contents permission
. This disables write permissions on your repositories by default. For more information on the permissions see permissions for the GitHub token.
You can find these settings at the following URL for your own repositories https://github.com/«your-organization»/«your-repo-goes-here»/settings/actions
.
In the remainder of this blog I will show you how to enable specific permissions in your workflows on a per need basis. That way your workflows will have only the bare minimum permissions required for your workflow, reducing the attack surface.
Simple release workflow
First let us have a look at a simple release workflow that builds our Docker images and publishes them to GitHub container registry.
We require the packages: write
permission to be able to push the image to ghcr.io
. Once you have executed this workflow a new repository is created with private
visibility. To continue with signing using rekor transparancy log you will have to make sure the docker repository is publicly accessible. You can enable this by navigating to your repo. In my case github.com/users/marcofranssen/packages/container/slsa-workflow-examples-docker/settings (please note you can't access this URL as you are no admin on this GitHub repository).
Signing our docker images
By adding signatures to our Docker images we make it possible to prove the authenticity of our Docker images using a cryptoghraphically verifyable signature. To add a signature to the Docker image we will add a sign job to the workflow and some additional configuration to capture the image digest for our next steps. To do so we will make use of cosign, rekor and fulcio. Enabling this feature with cosign simply requires setting the following environment variable COSIGN_EXPERIMENTAL=1
. We will do this on workflow level by adding this environment variable at workflow level.
See below the other parts added to the workflow. First take some time to digest these changes, I will explain further below the code block.
At the docker
job level we configured 1 outputs that allows to pass some information to other jobs. To retrieve this information we add another step in the Docker job that retrieves this information and writes it to the outputs.
Next we add a new job that takes care of signing the Docker image. To be able for this job to authenticate with Fulcio/Rekor we have to enable the id-token
permission with write
access (remember we disabled all permissions by default on our repositories). Using this experimental feature (at the time of writing this blog) we can enable keyless code-signing by using an OIDC integration between GitHub and Sigstore. The Docker login is required as cosign will store the signature in the OCI registry. See the workflow execution notices on how to verify and inspect the signature of our release.
Before you continue, you workflow should look like this at this stage.
Generate an SBOM
By adding an SBOM we can provide the consumers of our Docker image with an extensive list of our dependencies. At release time our image might not have any known vulnerabilities, but in a couple of weeks/months from now there might be known vulnerabilties with the dependencies in our image. Adding an SBOM allows the consumers of our image to verify if our release contains known vulnerabilities at any future point in time. As well they can inspect the LICENSES involved in our release.
Same story here. To be able to attest the SBOM to our image we need to have the OIDC integration between GitHub and Sigstore, hence the permissions
. To generate an SBOM from our Docker image we use syft. The notice logged at the end, gives a nice log message in the workflow run overview so people know how to retrieve and inspect the SBOM for this particular release. See the workflow execution notices
Before you continue you can check here for the current state of our workflow.
Add build provenance
To add build provenance we will make use of slsa-provenance-action. SLSA provenance action
allows us to generate build provenance according to the SLSA provenance specification which uses in-toto predicates. in-toto
is a framework to secure the integrity of software supply chains.
To be able to store all released tags in the build provenance we need one more output that captures the tags we released.
Add the following to the docker
job.
Now at the end of the workflow add the following to generate build provenance.
To be able to attest the provenance to our image we also need the permissions
block here for the OIDC integration between GitHub and Sigstore. To generate the provenance we make use of the philips-labs/slsa-provenance-action. This action generates the build provenance based on your GITHUB_CONTEXT
and RUNNER_CONTEXT
which are environment variables known within a GitHub workflow execution. In the provenance we also capture which Docker tags have been released.
The notice logged at the end, gives a nice log message in the workflow run overview so consumers of the image know how to retrieve and inspect the build provenance for the release.
Summary
The full example can be found here. In the example you will find a small Go app with a single dependency installed to showcase SBOM. See here the end result in action.
With the signature, SBOM and provenance in place, we can now start building policies on our production environments that verify against the signatures and attestations before allowing a deployment.
What other improvements/practices do you suggest to reach a higher SLSA Level? Feel free to contribute to the example, by forking the repository, to help others.
Bonus
Using crane you can easily query the OCI registry to see how the signature and attestations are stored for your image.