Can you prove that your project's artifacts were built by a trusted authority? Do you have assurance in the supply chain security of your project's artifacts?

An important piece of the this puzzle is container image and artifact signing and verification.

cosign is a tool from the sigstore project for signing and verifying blobs and container images, in an easy and simple way.

🖊 Signing your artifacts

cosign supports three types of keys

  • key-pair
  • key-pair in KMS
  • OIDC

There may by different reasons for why a particular type of key is needed for a usecase.

Key-pairs mean that a public and private key will need to be stored and tracked for usage.

The use of a KMS (key management system) is the same as key-pairs, except the storage of it is managed externally.

OIDC means for an ephemeral key, used only for that signing instance.

🔑 Using key-pairs

Key-pairs are generated with the following command

cosign generate-key-pair

resulting in cosign.pub and cosign.key files.

A container image is able to be signed then with the following command

cosign sign --key cosign.key IMAGE

and verified with

cosign verify --key cosign.pub IMAGE

A blobs (any file) is able to be signed with the following

cosign sign-blob --key cosign.key --bundle FILE.bundle FILE

and verified with

cosign verify-blob --key cosign.pub --bundle FILE.bundle FILE

đŸĒĒ Using OIDC

OIDC allows for the use of identites to sign resources, supported providers include

  • GitHub
  • Google
  • Microsoft
  • GitLab

Locally, I use the GitHub provider but in a CI I use either the GitLab or GitHub provider.

To sign with OIDC, use the following command

cosign sign IMAGE

and a page launches on your local web browser for choosing a provider to use your identity to sign the resource.

an image can be verified with a similar command to the following

cosign verify \
    --certificate-identity-regexp 'https://gitlab.com/BobyMCbobs/.*//.gitlab-ci.yml@(refs/heads/main|refs/tags/.*)' \
    --certificate-oidc-issuer-regexp 'https://gitlab.com' \
    -o text \
    IMAGE

or if you don't know the expected identity+issuer just yet, you may like to use

cosign verify \
    --certificate-identity-regexp '.*' \
    --certificate-oidc-issuer-regexp '.*' \
    -o text \
    IMAGE

🚀 How I use it in production

In GitLab CI it might look something like this for a Go project

stages:
  - build

build:
  image: docker.io/golang:1.21.6-alpine3.19@sha256:a6a7f1fcf12f5efa9e04b1e75020931a616cd707f14f62ab5262bfbe109aa84a
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: "sigstore" # NOTE: important for enabling OIDC
  before_script:
    - apk add cosign git
    - ko login "${CI_REGISTRY}" -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}"
    - export KO_DOCKER_REPO="${CI_REGISTRY,,}"
  script:
    - IMAGE=$(ko build .)
    - cosign sign --yes "$IMAGE"

(docs.gitlab.com/ee/ci/yaml/signing_examples.html)

or in GitHub Actions, like this

name: build
on:
  push: {}
permissions:
  contents: read
  id-token: write
  packages: write
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
      - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
        with:
          go-version: ${{ steps.run-info.outputs.go-version }}
      - uses: ko-build/setup-ko@ace48d793556083a76f1e3e6068850c1f4a369aa # v0.6
      - uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
      - name: build
        env:
          KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
        run: |
          export KO_DOCKER_REPO="${KO_DOCKER_REPO,,}"
          IMAGE=$(ko build .)
          cosign sign --yes "$IMAGE"          

(github.com/marketplace/actions/cosign-installer)

and in my production clusters I use sigstore's policy-controller, with policies like this

apiVersion: policy.sigstore.dev/v1alpha1
kind: ClusterImagePolicy
metadata:
  name: registry-gitlab-com-bobymcbobs-is-signed
spec:
  images:
  - glob: "registry.gitlab.com/bobymcbobs/**"
  authorities:
  - keyless:
      identities:
      - issuer: https://gitlab.com
        subjectRegExp: "https://gitlab.com/BobyMCbobs/.*//.gitlab-ci.yml@refs/heads/main"

which requires workloads to have signed images for anything that is from my GitLab registry namespace.

Or like this one to require Knative images to be signed

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: knative-signed
spec:
  images:
  - glob: gcr.io/knative-releases/**
  authorities:
  - keyless:
      url: https://fulcio.sigstore.dev
      identities:
      - issuer: https://accounts.google.com
        subject: signer@knative-releases.iam.gserviceaccount.com
    ctlog:
      url: https://rekor.sigstore.dev

(original author Chainguard: github.com/chainguard-dev/policy-catalog/â€Ļ/policies/sigstore-landscape/knative-signed.yaml)

👀 Other cool things

  • Sigstore is publicly auditable and witnessable
  • designed around ease of use and security (sigstore.dev/â€Ļ/threat-model)
  • see the secure software supply chain artifacts with cosign tree IMAGE

đŸ‘Ĩ Who's using Sigstore and Cosign?

and many many others!

I personally use it anytime I need to publish an artifact, such as the container image for this website.

✨ Closing

With the ever-increasing importance of securing a project's supply chain, it's really helpful how easy it is to implement cosign in artifact release and verification. Many large Open Source projects, such as ones hosted by the CNCF have adopted it, providing users with a level of safety and security when consuming their project's artifacts.

Check out the docs here: docs.sigstore.dev

If this post is interesting to you, perhaps you might like to also checkout my presentation for this topic here!

Thank you for reading this post and hope it helped you!