Policy at the Front Door – Admission Governance (Gatekeeper)
Duration: ~25 minutes
Overview
Admission governance & policy layering. Enforce baseline safety gates before insecure workloads enter the cluster.
Use Gatekeeper admission policies to block unsafe cluster changes at the API and show clear allow versus deny outcomes.
Why it matters
Insecure specs enter the cluster only once—at admission. Gatekeeper (and similar) policies turn “we hope teams follow the guide” into denies at the API before a rootful or hostPath payload ever schedules.
What does it solve
-
Workloads that quietly revive root, privileged, or host mounts
-
Policy drift between wiki docs and what the API accepts
-
Late discovery of unsafe objects already running in production
Your Mission
Install admission gates that block attacker-shaped pod specs at the door and prove clear allow versus deny outcomes.
Prerequisites
-
Gatekeeper / OpenShift operator installed (CRDs: ConstraintTemplate, K8sAllowedRepos, etc.)
-
Cluster admin privileges
Click each step only if you need a hint.
Namespace Setup
oc new-project 201-11-a-govern
No Root UID Constraint
ConstraintTemplate:
oc apply -f - <<'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snonrootuid
spec:
crd:
spec:
names:
kind: K8sNonRootUid
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snonrootuid
violation[{"msg": msg}] {
input.review.kind.kind == "Pod"
c := input.review.object.spec.containers[_]
not c.securityContext.runAsNonRoot
msg := sprintf("Container %s must set runAsNonRoot=true", [c.name])
}
EOF
Constraint:
oc apply -f - <<'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNonRootUid
metadata:
name: nonroot-required
spec: {}
EOF
Test (expect denial):
oc apply -n 201-11-a-govern -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: root-pod
spec:
containers:
- name: app
image: registry.access.redhat.com/ubi9/ubi
securityContext:
runAsNonRoot: false
command: ["sleep","5"]
EOF
Mandatory NetworkPolicy Constraint
Template:
oc apply -f - <<'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snetpolrequired
spec:
crd:
spec:
names:
kind: K8sNetPolRequired
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snetpolrequired
violation[{"msg": msg}] {
input.review.kind.kind == "Pod"
ns := input.review.object.metadata.namespace
not has_network_policy[ns]
msg := sprintf("Namespace %s lacks a NetworkPolicy", [ns])
}
has_network_policy[ns] {
p := data.inventory.namespace[ns].networkpolicies[_]
}
EOF
Constraint:
oc apply -f - <<'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNetPolRequired
metadata:
name: netpol-required
spec: {}
EOF
Create a pod (should be denied). Then add a minimal NetworkPolicy and retry:
oc apply -n 201-11-a-govern -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-default
spec:
podSelector: {}
policyTypes: [Ingress]
EOF
Retry pod with non-root context:
oc apply -n 201-11-a-govern -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: ok-pod
spec:
containers:
- name: app
image: registry.access.redhat.com/ubi9/ubi@sha256:123fake
securityContext:
runAsNonRoot: true
command: ["sleep","1000"]
EOF
Digest Pinning & (Simulated) Signature Constraint
Simplified example focusing on forbidding mutable tags: Template:
oc apply -f - <<'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdigestonly
spec:
crd:
spec:
names:
kind: K8sDigestOnly
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdigestonly
violation[{"msg": msg}] {
input.review.kind.kind == "Pod"
c := input.review.object.spec.containers[_]
startswith(c.image, "registry.access.redhat.com/")
not contains(c.image, "@sha256:")
msg := sprintf("Image %s must use digest", [c.image])
}
EOF
Constraint:
oc apply -f - <<'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDigestOnly
metadata:
name: digest-only
spec: {}
EOF
Test denial with tag:
oc apply -n 201-11-a-govern -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: tag-pod
spec:
containers:
- name: app
image: registry.access.redhat.com/ubi9/ubi:latest
securityContext:
runAsNonRoot: true
command: ["sleep","1"]
EOF
Namespace Exemption
oc label namespace 201-11-a-govern policy-tier=dev
# Example: refine rego to exempt tier=dev; left as exercise
Debrief
Admission governance blocks drift before runtime: constraints for non-root, required NetworkPolicy, digest/signing—and exemptions only via explicit, auditable labels.
What breaks without this:
-
Policy-only-at-runtime → bad configs already scheduled
-
Silent exceptions → untracked risk across teams
Controls that matter: Gatekeeper/Kyverno (or equivalent) constraint sets, progressive enforce, and labeled exemptions with owners and expiry.
Quick facts: admission is the last gate before the API persists the object—pair it with CI scanning so developers fail earlier.
