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 β
ββββββββββββββββββββββββββββββββ
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
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>
Verification:
cosign verify --key cosign.pub ghcr.io/<repo>/<image>:<tag>
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-----
At runtime Kyverno:
- Extracts image reference from Pod
- Resolves immutable digest
- Fetches Cosign signature from registry
- Verifies signature
- 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.
Top comments (0)