Trusted Sources Only – Guardrails Against Unapproved Images
Duration: ~15 minutes
Overview
Limit which registries the cluster may pull from, then confirm what you ran by registry host and sha256 digest.
Try a deployment from an untrusted source, block it with an allow-list, then deploy a trusted UBI image and verify host plus digest provenance.
Why it matters
Pulls from arbitrary registries mean unknown builders and unvetted contents. An allow-list keeps traffic on registries you trust and cuts a lot of avoidable CVE noise from random public tags.
What does it solve
-
Accidental pulls from arbitrary public defaults
-
Image provenance ambiguity
-
Higher vulnerability noise baseline
-
Reproducibility issues with
:latest
Your Mission
Supply-chain attackers ship malware through any registry that will take a tag. Your mission: put an allow-list on the door, prove an untrusted image is denied, then run only a trusted UBI workload you can fingerprint by host and digest.
Click each step only if you need a hint.
Mark the namespace as under registry lockdown
oc new-project 101-07-i-trusted
oc label namespace 101-07-i-trusted trusted-registry-enforce=true --overwrite
Install the allow-list (cluster-admin) — close arbitrary pulls
oc apply -f - <<'EOF'
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: allow-trusted-registries-pods
spec:
failurePolicy: Fail
matchConstraints:
matchPolicy: Equivalent
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE","UPDATE"]
resources: [pods]
validations:
- expression: "object.spec.containers.all(c,\n c.image.startsWith('registry.access.redhat.com') ||\n c.image.startsWith('registry.redhat.io') ||\n c.image.startsWith('quay.io') ||\n c.image.startsWith('image-registry.openshift-image-registry.svc')\n )"
message: "Pod rejected: only Red Hat registries, Quay.io, or internal registry images allowed."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: trusted-images-only-pods
spec:
policyName: allow-trusted-registries-pods
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
trusted-registry-enforce: "true"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: allow-trusted-registries-deployments
spec:
failurePolicy: Fail
matchConstraints:
matchPolicy: Equivalent
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE","UPDATE"]
resources: [deployments]
validations:
- expression: "object.spec.template.spec.containers.all(c,\n c.image.startsWith('registry.access.redhat.com') ||\n c.image.startsWith('registry.redhat.io') ||\n c.image.startsWith('quay.io') ||\n c.image.startsWith('image-registry.openshift-image-registry.svc')\n )"
message: "Deployment rejected: only Red Hat registries, Quay.io, or internal registry images allowed."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: trusted-images-only-deployments
spec:
policyName: allow-trusted-registries-deployments
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
trusted-registry-enforce: "true"
EOF
Launch the untrusted payload — expect Deny
oc -n 101-07-i-trusted create deployment bad --image=docker.io/library/nginx:latest || true
Deploy only from an approved registry
oc -n 101-07-i-trusted create deployment good --image=registry.access.redhat.com/ubi9/ubi:9.6 -- sleep infinity
oc -n 101-07-i-trusted wait --for=condition=Available deployment/good --timeout=60s
Fingerprint provenance — host + digest
Prove what actually runs so a floated :latest tag cannot quietly swap in attacker content.
oc -n 101-07-i-trusted describe pod -l app=good | grep -E 'Image:|Image ID:'
What to look for:
-
Image: trusted registry host (registry.access.redhat.com, registry.redhat.io, quay.io, or your internal registry)
-
Image ID: immutable sha256 digest (tags can move; digests cannot)
If both are correct, you have a basic provenance check: approved source and exact, verifiable content.
Example (UBI 9.6):
-
Image: registry.redhat.io/ubi9/ubi:9.6 (trusted source)
-
Image ID: …@sha256:dbc1e98d14a022542e45b5f22e0206d3f86b5bdf237b58ee7170c9ddd1b3a283 (immutable digest)
Cleanup
oc delete project 101-07-i-trusted --wait=false
oc delete validatingadmissionpolicybinding trusted-images-only-pods || true
oc delete validatingadmissionpolicybinding trusted-images-only-deployments || true
oc delete validatingadmissionpolicy allow-trusted-registries-pods || true
oc delete validatingadmissionpolicy allow-trusted-registries-deployments || true
Debrief
You marked a namespace for registry lockdown, blocked an untrusted pull, allowed an approved source, and fingerprinted host plus digest for provenance.
What breaks without this:
-
Arbitrary registries → unknown base images and supply-chain risk
-
Floating tags like
:latest→ non-reproducible, mutable runtime -
No allow-list → policy gaps between teams and clusters
Controls that matter: trusted-registry allow-lists (admission), digest pins for important workloads, progressive labels for opt-in, and later signature verification.
Quick facts: mirror public images into a registry you control; treat :latest as a demo habit, not production practice.
