DEV Community

Matteo Vitali
Matteo Vitali

Posted on

πŸ”Enforcing image provenance in Kubernetes using Cosign + Sigstore + Kyverno

What if your Kubernetes cluster simply refused to run unsigned images?

I spent some time experimenting with enforcing image provenance in a small Kubernetes setup using MicroK8s.

The idea was simple:

Only container images with valid cryptographic signatures are allowed to run in the cluster.

For this I used:

  • GitLab CI/CD (build + signing pipeline)
  • Cosign / Sigstore (image signing)
  • Kyverno (admission control)
  • MicroK8s (local cluster)

Repo: https://github.com/trottomv/microk8s-cosign-kyverno


Why this matters

Most Kubernetes setups still rely on mutable image tags like latest, which introduces supply chain risks:

  • No guarantee of image origin
  • No binding between CI pipeline and deployed artifact
  • Risk of registry or tag mutation

So the trust gap is basically:

build time β†’ registry β†’ runtime


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        GitLab CI/CD          β”‚
β”‚                              β”‚
β”‚  β€’ Build container image     β”‚
β”‚  β€’ Push to OCI registry      β”‚
β”‚  β€’ Sign image (Cosign)       β”‚
β”‚  β€’ Publish signature         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        OCI Registry          β”‚
β”‚   (image + signature)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Kubernetes (MicroK8s)      β”‚
β”‚                              β”‚
β”‚  Kyverno Admission Controllerβ”‚
β”‚  β€’ verify Cosign signature   β”‚
β”‚  β€’ resolve image digest      β”‚
β”‚  β€’ enforce policy decision   β”‚
β”‚                              β”‚
β”‚  β†’ allow / reject Pod        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

CI/CD: GitLab as trust origin

The image is built and signed inside GitLab CI/CD.

Example pipeline:

stages:
  - build
  - sign

build:
  stage: build
  script:
    - docker build -t $IMAGE:$TAG .
    - docker push $IMAGE:$TAG

sign:
  stage: sign
  script:
    - cosign sign --key $COSIGN_KEY $IMAGE:$TAG
Enter fullscreen mode Exit fullscreen mode

The rule is simple:

If it doesn’t come from CI, it doesn’t get signed.

This effectively makes the CI pipeline the root of trust for all artifacts.


Signing with Cosign

Cosign attaches a cryptographic signature to OCI images.

cosign sign --key cosign.key ghcr.io/<repo>/<image>:<tag>
Enter fullscreen mode Exit fullscreen mode

Verification:

cosign verify --key cosign.pub ghcr.io/<repo>/<image>:<tag>
Enter fullscreen mode Exit fullscreen mode

Signatures are stored directly in the OCI registry as artifacts.


Kyverno enforcement

Kyverno enforces image verification at admission time using a ClusterPolicy.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-signed-images
spec:
  validationFailureAction: Enforce
  background: false

  rules:
    - name: check-signature
      match:
        resources:
          kinds:
            - Pod

      verifyImages:
        - imageReferences:
            - "ghcr.io/<repo>/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |
                      -----BEGIN PUBLIC KEY-----
                      ...
                      -----END PUBLIC KEY-----
Enter fullscreen mode Exit fullscreen mode

At runtime Kyverno:

  1. Extracts image reference from Pod
  2. Resolves immutable digest
  3. Fetches Cosign signature from registry
  4. Verifies signature
  5. Allows or denies Pod creation

What happens in practice

Unsigned image

  • No valid signature found
  • Kyverno rejects Pod at admission

Signed image

  • Signature verified successfully
  • Pod is admitted into the cluster

The interesting bit:

even if someone manually pushes an image to the registry, it still won’t run.

The cluster doesn’t trust the registry β€” it trusts the signature.


Why MicroK8s

MicroK8s was used as a lightweight lab environment to:

  • test Kyverno policies locally
  • simulate admission control behavior
  • iterate quickly on Cosign integration

Key takeaways

  • GitLab CI/CD becomes a trust anchor when combined with Cosign
  • Kyverno enforces security at admission time, not runtime
  • OCI registry acts as a provenance carrier
  • End-to-end supply chain security is achievable with minimal tooling

Closing thought

This experiment shows how Kubernetes supply chain security can be enforced end-to-end using:

  • GitLab CI/CD for build and signing
  • Cosign for cryptographic provenance
  • Kyverno for policy enforcement

Result:

Only verified and signed workloads can run in the cluster.


Repository

https://github.com/trottomv/microk8s-cosign-kyverno

Top comments (0)