Lesson 10 of 15 · Workload & Supply Chain
Pod Security & admission control
Control what kind of workloads may run: Pod Security Standards enforced by built-in admission, a hardened securityContext, and custom rules with ValidatingAdmissionPolicy (CEL), Kyverno or Gatekeeper.
RBAC says who; admission says what
RBAC decides who may create a pod. Admission control decides what that pod may look like: can it run as root, mount the host filesystem, use the latest tag, skip resource limits? Admission runs after authentication and authorization, before the object is stored.
RBAC is the rule "only club members may bring a pet to school". Admission control is the vet at the gate who checks each pet anyway: no wild animals, must be on a lead, must have a name tag. Being allowed to bring a pet doesn't mean you can bring a tiger.
Pod Security Standards and Admission
Kubernetes defines three levels, enforced per namespace by the built-in Pod Security Admission:
| Level | Allows | Use for |
|---|---|---|
| privileged | Everything | System namespaces that truly need host access (CNI, storage, monitoring agents) |
| baseline | Blocks known privilege escalations (privileged, host namespaces, hostPath, extra capabilities…) | Minimum for all workloads |
| restricted | Baseline + must run as non-root, drop ALL capabilities, no privilege escalation, a seccomp profile | Default target for applications |
Each namespace can set three modes independently: enforce (reject), warn (tell the client), audit (record in the audit log).
$ kubectl label --dry-run=server --overwrite ns shop pod-security.kubernetes.io/enforce=restricted
Warning: existing pods in namespace "shop" violate the new PodSecurity enforce level "restricted:latest"
Warning: web-7f9c5d6b8d-4hx9t: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
namespace/shop labeled (server dry run)
A restricted-compliant pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: shop
spec:
replicas: 2
selector:
matchLabels: { app: api }
template:
metadata:
labels: { app: api }
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile: { type: RuntimeDefault }
containers:
- name: api
image: registry.example.com/shop/api:1.4.2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- { name: tmp, mountPath: /tmp } # writable scratch space
volumes:
- { name: tmp, emptyDir: {} }
Many public images run as root by default and fail under restricted. Prefer images built to run as a non-root user (e.g. nginxinc/nginx-unprivileged instead of nginx).
Custom rules: three options
1. ValidatingAdmissionPolicy (built in, CEL). No extra software; runs inside the API server:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-team-label
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
message: "Deployments must have a 'team' label"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-team-label
spec:
policyName: require-team-label
validationActions: ["Deny"] # or ["Warn", "Audit"] while rolling out
matchResources:
namespaceSelector:
matchLabels:
policy: enforced
2. Kyverno. Policies as YAML patterns; can also mutate (add defaults), generate (create objects) and verify image signatures:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce # newer Kyverno versions set this per rule
rules:
- name: require-pinned-tag
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Use a specific image tag or digest, not ':latest'."
pattern:
spec:
containers:
- image: "!*:latest"
3. OPA Gatekeeper. Policies in Rego via ConstraintTemplates; strong when you already use OPA elsewhere.
| ValidatingAdmissionPolicy | Kyverno | Gatekeeper | |
|---|---|---|---|
| Runs | In the API server | Webhook | Webhook |
| Language | CEL | YAML patterns (+ CEL) | Rego |
| Mutation / generation | Mutating policies are newer and still maturing | ✅ | Mutation ✅ |
| Image verification | ❌ | ✅ | via add-ons |
Webhooks are in the critical path
A policy webhook that's down can block all pod creation (with failurePolicy: Fail), including during an incident. Run webhook engines highly available, exclude kube-system and the engine's own namespace, and monitor webhook latency and errors.
Try it: enforce and fix
- Label a namespace
warn=restricted, deploynginx:1.27, and read the warnings. - Switch to
enforce=restricted: the Deployment's pods are rejected (seekubectl get rsand its events). - Fix it: use
nginxinc/nginx-unprivileged(listens on 8080) with the securityContext above, plus anemptyDirfor any paths nginx writes to. - Apply the
require-team-labelValidatingAdmissionPolicy in Warn mode, create a Deployment without the label, then switch to Deny. - (Optional) Install Kyverno and the
disallow-latest-tagpolicy; test withimage: busybox:latest.
Going deeper: policy as a platform product
- Keep policies in Git, test them in CI (Kyverno CLI,
gatorfor Gatekeeper, or kubectl dry-run with CEL), and roll them out warn → audit → enforce with a date communicated to teams. - Pair each policy with a fix guide and a compliant example in your golden path (see Kubernetes Administration, lesson 30).
- Use mutation sparingly: silently changing workloads surprises teams. Prefer validation with clear messages, plus good defaults in templates.
- Exceptions should be explicit objects (e.g. Kyverno PolicyExceptions) with owners and expiry dates.
Recap
- Pod Security Admission: namespace labels with levels privileged / baseline / restricted and modes enforce / warn / audit.
- A hardened pod: non-root, no privilege escalation, drop ALL, RuntimeDefault seccomp, read-only root filesystem.
- Custom rules: ValidatingAdmissionPolicy (CEL, in-process), Kyverno or Gatekeeper (webhooks).
- Roll out warn/audit first; run webhooks HA and keep system namespaces safe.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.