Published on December 15, 2021
Table of Contents
Overview
Vulnerabilities can be introduced at many points within the software delivery lifecycle, as we’ve learned the hard way through high profile hacks over the course of the past couple years. As a result, supply chain security has been a hot topic within the cloud native community, with the new Sigstore project getting significant press. Let’s take a look at some simple steps you could take to get started with the Sigstore tools.
You may already be aware of the challenge of maintaining cryptographic signatures in the container lifecycle. While Docker as a tool simplified the container build, distribution, and run process for end users, the signing mechanism is not easy to use. Fortunately, the new tools in this space are designed with end users in mind to help us create and manage signed images.
Our intention here is to ensure that software we are delivering to our production environment is exactly the software that was built. For software that is consumed as a dependency by external parties, we want to give them surety in our delivery process by maintaining a broad set of cryptographically signed metadata about the release artifact that can be easily validated.
Supply Chain Security Journey
Now, how do we know which steps will help us meaningfully improve the security of software delivery? This is where the SLSA Framework provides us with excellent guidance. This framework outlines a set of maturity levels for supply chain security. Introduced by Google’s Open Source Security Team, this framework provides incrementally adoptable guidelines for securing your supply chain.
Let’s take a look at what it takes to reach the first maturity level, SLSA Level 1. The framework describes this level as: The build process must be fully scripted/automated and generate provenance.
As a “cloud native” team, you already have a CI process in place to generate Docker images. All we need to do next is generate the provenance linking them to the build. Provenance is metadata about the build process and its source that must be associated with the constructed artifact. In SLSA Level 1, this provenance must include the following:
- The provenance MUST identify the output artifact via at least one cryptographic hash
- The provenance identifies the entity that performed the build and generated the provenance
- The provenance identifies the top-level instructions used to execute the build
Sounds complicated! Fortunately, Sigstore provides us with the tools we need to get this done.
Identify Output Artifact
The first requirement from SLSA level 1 we want to accomplish is to uniquely identify the output artifact via a cryptographic hash. For much of the software delivered over the internet, this hash is created by uploading a sha256sum file to the repository adjacent to the release artifact itself. While the presence of this file helps to know that the file you download matches the files that were released, it does not provide any additional information about the generation process for the artifact itself.
We can generate artifact provenance with cryptographic signatures using Sigstore’s Cosign tool, which stores provenance in an OCI registry. If your generated artifact is a Docker image, most container registries support the necessary APIs. For many teams deploying to Kubernetes, this is a great place to start since our software is packaged and delivered to the runtime environment via this registry. It’s super easy to get started with cosign, download the binary and try this out on one of your existing images:
cosign generate-key-pair
cosign sign –key cosign.key my-registry.io/my/image:tag
You’ve now got a signed image stored in the associated image registry!
As you just saw, Cosign must reference a key pair in order to sign an image. Key management is one of the hardest problems in the space of supply chain security. How do you know where to source your keys, what keys remain valid, and what users have access to use the keys?
This is one of the most powerful aspects of the Sigstore ecosystem; built-in support for a broad variety of key sources. Cosign keys can be sourced from the filesystem, Kubernetes secrets, any one of the public cloud KMS services, Hashicorp vault, even Github or Gitlab. This integration makes it likely that your build process already has access to a valid key source that cosign could use.
With these necessary pieces in hand, we can now add Cosign to the final step of our image publishing process. After the artifact is pushed to the registry, run the cosign sign
command passing in a reference to the supplied key and the URL of the registry image. The sign command will create the provenance metadata in the registry alongside the image itself. You have now satisfied the first criteria for SLSA level 1 compliance!
Identify Build Source
Signing the build artifact is only the first step, as we also need to provide provenance for the build user (or service account) and build instructions in order to achieve SLSA Level 1. As with everything in the SLSA ecosystem, we can approach this problem incrementally. Let’s start with a simple approach and see how we can improve.
First, we need to be able to record the build instructions that are used. You probably already have a build-as-code approach within your repository, whether using Makefiles, Jenkinsfiles, .github.yml, or other similar tools. This means that the source code itself acts as the interface between the externally executed build system and the build instructions. Given this approach, for SLSA Level 1 it is sufficient to establish a record of the exact state of the target repository at build time. In the case of a git repo, this record takes the form of a commit SHA.
The cosign sign
command can accept annotations on the generated signatures that we can use to point back to the source repository that was used to generate this artifact. Most build systems should have access to the commit hash and commit repository, so those two annotations make sense to provide the initial build instruction. For example, in Jenkins, the Git plugin automatically adds the GIT_COMMIT, GIT_BRANCH, and GIT_URL environment variables. In this example, we could run:
cosign sign -a commit=${GIT_COMMIT} \
-a branch=${GIT_BRANCH} \
-a repo=${GIT_URL} \
–key cosign.key \
my-registry.io/my/image:tag
The same approach could be used for identifying the build user. To satisfy SLSA Level 1, it would be sufficient to identify the build user by adding an annotation to the cosign sign command, embedding it within the resulting provenance. If you get to this point, you will have completed all the provenance requirements to achieve SLSA Level 1, congrats!
Next, we will explore some experimental features of cosign that allow us to authenticate the build user alongside the signature.
Using OIDC Identity
Cosign has an experimental feature available on the sign command that allows the build user to exchange an OIDC ID token for a temporarily valid signing key. Github Actions just publicly announced support for this workflow. Cosign also supports using OIDC identities from Google Cloud. You can also use the Google OIDC provider via device code flow, opening a browser and logging in using a Google identity, though this is less useful for a build process.
This OIDC identity support means that if your software artifact is generated from a system that has access to one of these two identity providers, you no longer need to manage keys! With our signature process being dependent on the presence of a key, how could this be possible? We have yet to discuss the other two projects within the Sigstore ecosystem, Fulcio and Rekor, which will provide us the mechanisms we need. The keyless signing mechanism relies on these two services on top of the tools that we have already discussed.
Fulcio is the service that is the initiator of the identity based signing mechanism. Fulcio is a root-CA combined with an API that exchanges this OIDC token information for short-lived signing certificates. At this time, the cosign experimental workflows call the Fulcio API at https://v1.fulcio.sigstore.dev
, which is a publicly maintained instance by the Sigstore team. These short-lived certificates are then used to sign the provenance discussed earlier. Since the signing certificate is associated with the OIDC identity, information about the identity issuer and the identity email address is also attached to the signature as metadata. This meets the SLSA level 1 criteria for identity provenance, and exceeds it by authenticating that identity before recording it within the resulting provenance document. Authenticating the build identity is a step within the SLSA Level 2 requirements.
After this signature process is complete, information about the artifact and identity used for signing is uploaded to the certificate transparency log service, Rekor. This means that we can compare artifacts we build to the logs created by these identities in Rekor and investigate discrepancies to ensure no unauthorized access has occurred. Because Rekor also stores the public key associated with the signature, our validation process also does not need to rely on a key management system. There is a publicly available Rekor instance running at https://rekor.sigstore.dev. If desired, the signing certificate can also be output to the filesystem so that there is no runtime dependency on the Rekor service to validate the artifact signature.
How do we take advantage of this? If we have access to one of the supported identities, we can change our build process to run:
COSIGN_EXPERIMENTAL=1 cosign sign my-registry.io/my/image:tag
The result of cosign sign
remains a valid signature attached to your image repository, but now the image has been signed by a short-lived certificate associated to the identity that was authenticated during signing. You can also check out an example of using the Github Actions identity to sign this way.
Get Started!
Completing the steps outlined above should allow you to achieve SLSA Level 1. As discussed, the framework is intended to be incremental, so all of these changes put us in a great place to implement the next levels. If you took advantage of the experimental OIDC features, you are well on your way to achieving SLSA Level 2.
As with any security measure, there is always a cost tradeoff to implementing these capabilities. Sigstore strives to reduce this cost and make the decision to add cryptographically signed and authenticated provenance far simpler.
In the next article on this topic, we will explore how to verify this provenance within a Kubernetes environment to ensure the containers that we run match exactly with the images created by our build system. We will also get hands on with the Rekor CLI to see how we can use the transparency log to validate the build metadata.