← Back to list

Enforcing Signed Container Images in Kubernetes Using Cosign & Kyverno (Helm-based Setup)

Securing the container supply chain has become a baseline requirement for production Kubernetes clusters. As clusters grow and multiple…

Hansaka Biyon · 2026-01-30 12:38 · 62 claps · 5.0 min read
#kubernetes #cosign #kyverno #ci-cd-pipeline #helm
Open on Medium ↗
Wiki topics: MAC · Macroeconomics ☁️ · DevOps & Cloud 🚆 · Urban & Transport

Enforcing Signed Container Images in Kubernetes Using Cosign & Kyverno (Helm-based Setup)

Securing the container supply chain has become a baseline requirement for production Kubernetes clusters. As clusters grow and multiple teams deploy workloads, admission-time controls act as guardrails, preventing untrusted images from reaching runtime.

This article explains how Cosign, Kyverno, and Harbor can work together to enforce image signature verification in Kubernetes. The approach is well-suited for enterprise environments with private registries, custom TLS certificates, and restricted access to public transparency logs.

Why Image Signing Matters

Image signing provides three fundamental security guarantees:

  1. Integrity Confirms that the container image has not been modified after it was built and signed.
  2. Authenticity Verifies that the image originates from a trusted source and not from an unknown or compromised registry.
  3. Deployment-Time Enforcement Prevents unsigned or improperly signed images from entering the Kubernetes cluster.

In this setup:

  • Cosign is responsible for signing and verifying images.
  • Harbor stores container images and their corresponding signatures.
  • Kyverno acts as a Kubernetes admission controller that enforces signature verification rules.

If an image does not meet the defined verification criteria, the deployment is blocked before the workload reaches runtime.

High-Level Architecture

Architecture Overview

Architecture Overview

The flow of image signing and verification works as follows:

  1. A container image is built and pushed to Harbor.
  2. The image is signed using Cosign.
  3. The signature is stored as an OCI artifact in the same Harbor repository.
  4. A deployment request is sent to Kubernetes.
  5. Kyverno intercepts the request during admission.
  6. Kyverno verifies the image signature using the Cosign public key.
  7. Only verified images are allowed to run inside the cluster.

This shifts supply-chain security enforcement directly into the Kubernetes control plane.

Prerequisites

The following components are required:

  • A running Kubernetes cluster
  • A private Harbor registry using a custom CA
  • Helm installed
  • Access to the kyverno namespace
  • kubectl configured with cluster access

Step 1: Generate Cosign Key Pair

Generate a Cosign key pair:

cosign generate-key-pair

This creates two files:

  • cosign.key → private key (used for signing images)
  • cosign.pub → public key (used for verification by Kyverno)

The private key should be kept secure. Only the public key is used by Kubernetes/Kyverno.

Step 2— Sign the Image Using Cosign

Before an image can be verified, it must be signed.

cosign sign --yes \
  --key cosign.key \
  --allow-insecure-registry \
  --registry-username <REGISTRY_USERNAME> \
  --registry-password <REGISTRY_PASSWORD> \
  --tlog-upload=false \
  <REGISTRY_HOST>/<PROJECT>/<IMAGE_NAME>:<IMAGE_TAG>

Key Notes

  • --tlog-upload=false avoids reaching out to public Rekor (useful in restricted networks).
  • --allow-insecure-registry is often required with internal CAs or when Harbor uses a non-public CA chain.
  • Cosign pushes the signature as an OCI artifact next to the image.

Step 3— Export the Harbor Root CA Certificate

Kyverno must trust Harbor’s TLS certificate in order to verify signed images.

The Harbor root CA can be extracted using:

openssl s_client -showcerts \
  -connect <HARBOR_REGISTRY_HOST>:<PORT> </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > <HARBOR_CA_FILE>.pem

Example:

openssl s_client -showcerts \
  -connect registry.example.com:443 </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > harbor-root-ca.pem

This command generates a PEM-formatted CA certificate that will later be injected into Kyverno.

Step 4— Install Kyverno

Helm Chart Version vs Application Version (Important)

A common mistake when installing Kyverno is attempting to use the application version instead of the Helm chart version.

For example, the following installation attempt will fail:

helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --version v1.16.0

This fails because Helm installs chart versions, not application versions.

Kyverno maintains two separate versions:

  • Chart Version → Used by Helm
  • App Version → Kyverno controller version packaged inside the chart

Finding the Correct Chart Version

List all available Kyverno chart versions:

helm search repo kyverno -l

The output will look similar to this:

CHART VERSION    APP VERSION
3.6.0            v1.16.0

Here:

  • 3.6.0 is the Helm chart version
  • v1.16.0 is the Kyverno application version inside the chart

Correct Installation Command

Install Kyverno using the chart version:

helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --version 3.6.0

This installs Kyverno successfully with application version v1.16.0.

Verify the Installation

kubectl get pods -n kyverno

All Kyverno components should be in a running state before proceeding.

Step 5— Configure CA Trust for Kyverno

Kyverno must trust:

  • Harbor’s custom CA
  • The Cosign public key used for signature verification

Create a CA Bundle ConfigMap

kubectl -n kyverno create configmap kyverno-ca-bundle \
  --from-file=ca-certificates.crt=harbor-ca.pem

Create Secrets for Harbor CA and Cosign Public Key

kubectl create secret generic harbor-ca \
  --from-file=ca.crt=harbor-ca.pem \
  -n kyverno
kubectl create secret generic cosign-pub \
  --from-file=cosign.pub=cosign.pub \
  -n kyverno

Step 6— Enable CA Bundle Through Helm

The recommended production approach is to inject trusted certificates using Helm values.

Create a values override file values-ca-bundle.yaml:

config:
  caBundle:
    enabled: true
    configMapName: kyverno-ca-bundle
    mountPath: /etc/ssl/certs

Upgrade the Kyverno installation:

helm upgrade kyverno kyverno/kyverno \
  -n kyverno \
  --version 3.6.0 \
  -f values-ca-bundle.yaml

Step 7— Patch Kyverno to Trust Harbor CA (Testing Only)

kubectl patch deployment kyverno-admission-controller -n kyverno \
  --type='json' \
  -p='[
    {
      "op": "add",
      "path": "/spec/template/spec/volumes/-",
      "value": {
        "name": "harbor-ca",
        "secret": {
          "secretName": "harbor-ca"
        }
      }
    },
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/volumeMounts/-",
      "value": {
        "name": "harbor-ca",
        "mountPath": "/etc/ssl/certs/harbor-ca.crt",
        "subPath": "ca.crt"
      }
    }
  ]'

Restart Kyverno:

kubectl rollout restart deployment kyverno-admission-controller -n kyverno

If Kyverno is upgraded via Helm, or if the Helm chart is applied again, Helm overwrites the Deployment specification with what is defined in values.yaml.

As a result:

  • The manual patch may disappear silently
  • Any CA mounted via patching may no longer be available
  • Image signature verification may start failing after the upgrade

Step 8— Enforce Cosign Signature Verification with Kyverno

Create a Kyverno ClusterPolicy:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cosign-signatures  #<POLICY_NAME>
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-uat-images  #<RULE_NAME>
      match:
        resources:
          kinds:
            - Pod
          namespaces:
            - <TARGET_NAMESPACE>
      verifyImages:
        - imageReferences:
            - "<REGISTRY_HOST>/<PROJECT>/*"
          attestors:
            - entries:
                - keys:
                    secret:
                      name: cosign-pub
                      namespace: kyverno
          mutateDigest: false
          verifyDigest: false
          useCache: true

Important Note on **validationFailureAction**

The field validationFailureAction controls how Kyverno reacts when a policy violation occurs.

validationFailureAction: Enforce

Enforce :- The request is blocked if the image does not meet the verification rules.

Alternatively, Kyverno also supports:

validationFailureAction: Audit

Audit:- The request generates warnings not block the request

Apply the policy:

kubectl apply -f require-cosign-signatures.yaml

Restart Kyverno:

kubectl rollout restart deployment kyverno-admission-controller -n kyverno

Expected Behavior After Enforcement

  • Unsigned images → Deployment blocked
  • Images signed with an untrusted key → Deployment blocked
  • Images outside the allowed registry scope → Deployment blocked
  • Correctly signed images → Deployment allowed

Verification occurs before pod creation, eliminating runtime risk.

Production Tips and Good Practices

1. CI/CD Integration (Cosign in pipeline)

  • Sign at the end of the build once tests and scans pass.
  • Example GitHub Actions step:
- name: Cosign Sign
  env:
    COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
  run: |
    cosign sign --yes \
      --key cosign.key \
      --registry-username "${{ secrets.REGISTRY_USER }}" \
      --registry-password "${{ secrets.REGISTRY_PASS }}" \
      --tlog-upload=false \
      $REGISTRY/$PROJECT/$IMAGE:$TAG

2. Namespacing and Multi-Tenancy

  • For tenant isolation, scope policies to specific namespaces and registry paths.
  • If different teams use different keys, create multiple attestors entries or separate rules per team.

Conclusion

Using Cosign, Harbor, and Kyverno together creates a strong and practical supply-chain security model for Kubernetes. This configuration ensures that:

  • Container images are cryptographically signed
  • Only trusted images are admitted into the cluster
  • Private registries with custom CAs are fully supported
  • Security is enforced automatically at admission time

This approach significantly reduces the risk of unauthorized or compromised workloads running in sensitive Kubernetes environments.


메타데이터
post_id
646209ecb8ce
slug
enforcing-signed-container-images-in-kubernetes-using-cosign-kyverno-helm-based-setup-646209ecb8ce
url
https://medium.com/@hansakabiyon99/enforcing-signed-container-images-in-kubernetes-using-cosign-kyverno-helm-based-setup-646209ecb8ce
canonical_url
https://medium.com/@hansakabiyon99/enforcing-signed-container-images-in-kubernetes-using-cosign-kyverno-helm-based-setup-646209ecb8ce
author_url
https://medium.com/@hansakabiyon99
status
ok
fetched_at
2026-07-13 06:23:13