Sign and verify container images with this open source tool

The sigstore project aims at securing supply chain technology.
39 readers like this.
Parts, modules, containers for software

Opensource.com

Many open source software projects get used in software builds every day, which is critical for almost every organization. Open source software brings many benefits and helps software developers focus on innovation and efficiency rather than reinventing the wheel.

Sometimes, you cannot identify and verify the integrity of the third-party software used by constantly doing verification, which can open the door to supply chain attacks. Hence, the sigstore project was born. The sigstore project aims at securing supply chain technology and eventually the open source software security itself.

The sigstore project consists of several open source components under its umbrella: 

  • Fulcio (Root CA for Code Signing)
  • Rekor (Immutable tamper-resistant ledger for record signed metadata)
  • Cosign (Container signing, verification, and storage in OCI compliant registry)

In this article, I look at the cosign part from the project and how you can use it to sign and verify container images (and other supported objects). The philosophy of cosign is to make the signing and verifying process an immutable infrastructure to the developers.

Install and build cosign

In this example, I am going to install cosign on a macOS-based system. First, ensure the system has Docker installed and running as well for managing container images.

Using brew, I install cosign.

$ brew install sigstore/tap/cosign

==> Installing sigstore/tap/cosign
?  /usr/local/Cellar/cosign/1.3.1: 3 files, 82.5MB, built in 2 seconds

Next, I make sure I am logged in to the target registry, in this example to docker.io.

$ docker login docker.io

Login Succeeded

Sign and verify container images

Before I can sign and verify any images, I need to generate a public and private key pair. I then use this private key to sign the object and later verify it using the corresponding public key. I should also use a strong password to protect the key pair. Ideally, this password gets stored in the vault for security and audit purposes.

$ cosign generate-key-pair
Enter password for private key:
Enter again:
Private key written to cosign.key
Public key written to cosign.pub

Since I now have the required key to start signing, I sign our test image that I previously pushed into the registry. 

$ cosign sign --key cosign.key docker.io/mzali/test:latest
Enter password for private key:

Using the triangulate option, I can locate the cosign image reference from the registry.

$ cosign triangulate docker.io/mzali/test:latest
index.docker.io/mzali/test:sha256-25ca0d9c2f4e70ce3bfba7891065dfef09760d2cbace7a2d21fb13c569902133.sig

Now, this is the part where I want to verify the image against the public key and verify the signature's authenticity. Using the public key, I can verify the image signing key signature.

$ cosign verify --key cosign.pub docker.io/mzali/test:latest

Verification for index.docker.io/mzali/test:latest --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
  - Any certificates were verified against the Fulcio roots.

[{"critical":{"identity":{"docker-reference":"index.docker.io/mzali/test"},"image":{"docker-manifest-digest":"sha256:25ca0d9c2f4e70ce3bfba7891065dfef09760d2cbace7a2d21fb13c569902133"},"type":"cosign container image signature"},"optional":null}]

Do the same for SBOM

Next, I want also to sign the SBOMs. In this example, I use alpine:latest image to show how you can do it.

The alpine container image already got pushed to the registry. I first need to generate the SBOM from the image, and I use the syft binary from the syft project. Since I'm testing on a macOS-based system, I am going to use brew to install it.

$ brew tap anchore/syft
$ brew install syft

I check to see what packages are inside the alpine container image in the registry using syft

$ syft packages mzali/alpine        
 ✔ Loaded image            
 ✔ Parsed image            
 ✔ Cataloged packages      [14 packages]

NAME                    VERSION      TYPE 
alpine-baselayout       3.2.0-r16    apk   
alpine-keys             2.4-r0       apk   
apk-tools               2.12.7-r0    apk   
busybox                 1.33.1-r6    apk   
ca-certificates-bundle  20191127-r5  apk   
libc-utils              0.7.2-r3     apk   
libcrypto1.1            1.1.1l-r0    apk   
libretls                3.3.3p1-r2   apk   
libssl1.1               1.1.1l-r0    apk   
musl                    1.2.2-r3     apk   
musl-utils              1.2.2-r3     apk   
scanelf                 1.3.2-r0     apk   
ssl_client              1.33.1-r6    apk   
zlib                    1.2.11-r3    apk

Now I am about to sign this SBOM. I want the SBOM as SPDX 2.2 tag-value formatted (or another supported format, in this example, I choose SPDX format) and then attach it to the image.

$ syft packages mzali/alpine -o spdx  > latest.spdx
 ✔ Loaded image            
 ✔ Parsed image            
 ✔ Cataloged packages      [14 packages]

$  ls -lrt latest.spdx 
-rw-r--r--  1 yanked  yanked  10058 Nov 17 15:52 latest.spdx

$ cosign attach sbom --sbom latest.spdx mzali/alpine 
Uploading SBOM file for [index.docker.io/mzali/alpine:latest] to [index.docker.io/mzali/alpine:sha256-5e604d3358ab7b6b734402ce2e19ddd822a354dc14843f34d36c603521dbb4f9.sbom] with mediaType [text/spdx].

Using the digest output from the above, I sign the SBOM in the registry and verify it.

$ cosign sign --key cosign.key docker.io/mzali/alpine:sha256-5e604d3358ab7b6b734402ce2e19ddd822a354dc14843f34d36c603521dbb4f9.sbom 
Enter password for private key: %     

$ cosign verify --key cosign.pub docker.io/mzali/alpine:sha256-5e604d3358ab7b6b734402ce2e19ddd822a354dc14843f34d36c603521dbb4f9.sbom 

Verification for index.docker.io/mzali/alpine:sha256-5e604d3358ab7b6b734402ce2e19ddd822a354dc14843f34d36c603521dbb4f9.sbom --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
  - Any certificates were verified against the Fulcio roots.

[{"critical":{"identity":{"docker-reference":"index.docker.io/mzali/alpine"},"image":{"docker-manifest-digest":"sha256:00a101b8d193c294e2c9b3099b42a7bc594b950fbf535d98304d4c61fad5491b"},"type":"cosign container image signature"},"optional":null}]

What's next?

The simplest way to use cosign is to include this into your SDLC pipeline, as examples of Jenkins or Tekton tools. Using cosign, I can include it into the build process to sign and verify my software. If you are using Kubernetes, there is a Kubernetes cosigned admission controller that can look at your image signature and compare it against the specified public key. If the image is unsigned or uses an unknown key, the admission controller blocks it due to the violation:

$ kubectl apply -f unsigned-deployment.yaml
Error from server (BadRequest): error when creating "unsigned-deployment.yaml": admission webhook "cosigned.sigstore.dev" denied the request: validation failed: invalid image signature: spec.template.spec.containers[0].image

And again, cosign is just the tip of the iceberg from the whole sigstore project. These components are collaborative, integrate, and provide tampering resistance, strong verification points, and secure the software much easier using the same standard!

What to read next
User profile image.
Muhammad (also known as Din) has spent almost 15 years in the IT industry at organisations including a PCI-DSS secure hosting company, a system integrator, a managed services organisation, and a principal vendor. He has a deep interest in emerging technology especially in containers and the security domain.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.