If you prefer learning by watching rather than reading, the full lab walkthrough is available in video form on my YouTube channel.
Kubernetes Image Security with Kyverno (Real-World Lab) - Part 1
Controlling which container registries can be used inside a Kubernetes cluster is a core part of supply chain security. If workloads can pull images from any external source, you lose visibility and risk introducing untrusted software. A simple way to lock this down at admission time is to use Kyverno.
This guide walks through building policies that only allow images from approved registries and block everything else. The same approach applies in real production clusters and aligns well with CKS preparation.
Objective
Only permit images from:
registry.company.io
harbor.internal.local
Any Pod pulling from an unapproved registry, including Docker Hub, should be denied during admission.
Confirming the Active Cluster Context
Before you start, verify you're working on the correct cluster:
kubectl config current-context
kubectl get nodes
If the context looks wrong, switch:
kubectl config use-context <context>
kubectl get nodes
Move forward once your nodes show Ready.
Installing Kyverno
Kyverno operates as a set of controllers inside the cluster. If it's not already deployed, install it with Helm.
Check whether it exists:
kubectl get pods -n kyverno
If nothing shows up, add the repo:
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
Create the namespace if needed:
kubectl create namespace kyverno
Install Kyverno:
helm install kyverno kyverno/kyverno -n kyverno
Verify the controllers are running:
kubectl get pods -n kyverno
Once everything is Running, Kyverno begins intercepting admission requests.
Building the Global Registry Restriction Policy
Create a ClusterPolicy that validates image sources at admission.
File: restrict-registries.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: enforce
background: true
rules:
- name: validate-registries
match:
resources:
kinds:
- Pod
validate:
message: "Only registry.company.io or harbor.internal.local are allowed."
pattern:
spec:
containers:
- image: "registry.company.io/* | harbor.internal.local/*"
Apply the policy:
kubectl apply -f restrict-registries.yaml
Check registration:
kubectl get clusterpolicy
You should see restrict-image-registries in the list.
Testing the Policy
You need to validate both failure and success paths.
Test 1: Disallowed registry
kubectl run testbad --image=nginx
This is denied because the image does not match the allowed patterns. The admission error confirms the rule is active.
Test 2: Allowed registry
kubectl run testgood --image=registry.company.io/app:v1
kubectl get pods
Admission succeeds. The Pod may still fail to pull if the registry does not exist, but that is unrelated to the policy.
This proves the global restriction works.
Helpful Verification Commands
kubectl get clusterpolicy
kubectl describe clusterpolicy restrict-image-registries
kubectl get pods
kubectl describe pod testbad
kubectl describe pod testgood
Optional Cleanup
kubectl delete pod testbad testgood --ignore-not-found
Extending the Model for Multi-Environment Clusters
Most real clusters run several environments under the same control plane. A common setup is separate dev and prod namespaces. Each environment may rely on different registries, so policy enforcement must reflect that.
The global policy stays in place. Now add namespace-specific rules.
Creating Environment Namespaces
kubectl create namespace dev
kubectl create namespace prod
kubectl get namespaces
Applying Namespace-Based Registry Policies
Create a second ClusterPolicy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: registry-per-namespace
spec:
validationFailureAction: Enforce
background: true
rules:
- name: dev-registry-only
match:
resources:
kinds:
- Pod
namespaces:
- dev
validate:
message: "Dev namespace must use registry.dev.company.io."
pattern:
spec:
containers:
- image: "registry.dev.company.io/*"
- name: prod-registry-only
match:
resources:
kinds:
- Pod
namespaces:
- prod
validate:
message: "Prod namespace must use registry.prod.company.io."
pattern:
spec:
containers:
- image: "registry.prod.company.io/*"
Apply it:
kubectl apply -f registry-per-namespace.yaml
Testing Namespace Restrictions
Dev namespace
kubectl run bad-dev --image=nginx -n dev
This should be rejected.
kubectl run good-dev --image=registry.dev.company.io/app:1.0 -n dev
This is accepted.
Prod namespace
kubectl run bad-prod --image=nginx -n prod
kubectl run good-prod --image=registry.prod.company.io/web:1.0 -n prod
The nginx Pod fails. The registry-approved one passes admission.
How the Layered Approach Works
The cluster-wide policy limits image usage to internal registries only. The per-namespace policy adds a second filter:
dev uses only dev registry images
prod uses only prod registry images
Both policies apply together. A Pod must satisfy both to be admitted. This structure prevents accidental cross-environment deployments and tightens supply chain controls without complicating developer workflows.
Summary
You installed Kyverno, created a global restriction policy for image registries, and validated correct admission behavior. You then extended the scenario with environment-specific rules, ensuring dev and prod cannot cross-pull container images. This is a core security pattern for Kubernetes and aligns directly with CKS study requirements. Once registry control is established, you can layer in tag policies, digest enforcement, and signing rules.
Top comments (0)