GitOps a Hardened Python Image (Red Hat catalog)
Duration: ~25 minutes
Overview
201-11 Tampering: unsigned or floating tags get admitted. This lab deploys a Red Hat catalog Python image from Red Hat Images (registry.access.redhat.com/ubi9/python-311) as GitOps YAML, pinned to a digest, with no automounted SA token.
That image is signed by Red Hat, runs as non-root, and is the workload 301-04 will police at runtime. It is not Docker Hub python:alpine and not the workshop python-alpine-golden FROM used in 201-04.
If OpenShift GitOps (Argo CD) is installed, the same directory is what an Application would sync. This roadshow applies it with oc so you do not need a public Git remote.
Why it matters
Click-ops Deployments on :latest are how a registry swap becomes production. Git + digest is the reviewable, auditable path (Repudiation as well as Tampering). UBI Python is the enterprise default for a small HTTP service you can still exec into for runtime policy tests.
What does it solve
-
Mutable tags that silently change what the cluster runs
-
Public Python bases that are not in your approved catalog
-
GitOps theater (Argo installed, workloads still created in the console)
-
ServiceAccounts that still automount tokens on a simple app
Your Mission
Resolve the UBI Python digest, apply the in-repo manifests, prove the running image is digest-pinned, and (optional) show Argo CD would sync the same path.
Prerequisites
-
oclogged in with permission to create namespaces and Deployments -
Egress to
registry.access.redhat.com(or a cluster mirror of UBI) -
Optional: OpenShift GitOps Operator (
applications.argoproj.io)
Click each step only if you need a hint.
Part A: Catalog image
Look up the image on Red Hat Images
Open images.redhat.com and search for Python / ubi9/python-311. The pull spec this lab uses is registry.access.redhat.com/ubi9/python-311 (the same catalog image; registry.redhat.io if your cluster is authenticated to the entitlements registry).
Catalog pages show architecture, security metadata, and the recommended pull spec. Do not switch this lab to docker.io/library/python.
|
Resolve the digest
oc image info registry.access.redhat.com/ubi9/python-311:latest --filter-by-os=linux/amd64
Note the Digest: sha256:… line. That is what GitOps should pin. If oc image info fails, the apply script falls back to the tag and prints a warning.
|
Part B: Apply desired state
Apply the pinned manifests
YAML lives in this roadshow repo (setup/gitops-lab/). The helper rewrites the Deployment image to image@sha256:… then oc apply.
cd ~/openshift-security-roadshow/setup/gitops-lab
chmod +x apply-pinned.sh
./apply-pinned.sh
Applied python-app in 301-02-python using registry.access.redhat.com/ubi9/python-311@sha256:….
|
Wait for the pod and confirm the digest
oc rollout status deploy/python-app -n 301-02-python --timeout=120s
oc get deploy python-app -n 301-02-python -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
oc get sa app -n 301-02-python -o jsonpath='{.automountServiceAccountToken}{"\n"}'
oc get pods -n 301-02-python -o jsonpath='{.items[0].status.containerStatuses[0].imageID}{"\n"}'
Deployment image contains @sha256:. SA automount is false. imageID is the same digest the node pulled.
|
Hit the app from inside the cluster
oc exec deploy/python-app -n 301-02-python -- python3 -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8080/', timeout=5).status)"
Prints 200. The process is python3; ACS can still see exec in 301-04/05.
|
Part C: GitOps operator (optional)
If Argo CD is present, inspect it — do not require a new Git remote
oc get crd applications.argoproj.io 2>/dev/null && oc get argocd -A || echo "OpenShift GitOps not installed — oc apply -k is the GitOps path for this lab"
ls ~/openshift-security-roadshow/setup/gitops-lab
The kustomize directory is the desired state. On a customer cluster you would point an Application at this path in Git. You already applied it; skip creating an Application that needs a clone URL.
|
Debrief
GitOps of a Red Hat catalog Python image by digest is Tampering control at deploy time. Runtime is 301-04. Provenance depth (SBOM, signatures, admit unsigned) is 301-06.
What breaks without this:
-
:latestin production → yesterday’s scan is not what ran today -
Docker Hub Python in a “hardened” program → you skipped the catalog you already pay for
-
Policy YAML only on the hub, workloads still click-ops → two sources of truth
pin digest, UBI Python from images.redhat.com, no automount. Leave 301-02-python running if you are continuing to 301-04.
|
