Frontend Labs
⋯

Chapter 18 — Admission control and policy

In one sentence

Admission controllers inspect (and can mutate or reject) every object before it persists, and a policy engine there is how you enforce "no privileged pods", "all images signed", "every namespace has a NetworkPolicy", and hundreds of other rules that RBAC can't express.

Where we are

RBAC (Ch 17) decides who can create a Deployment. It can't say what a Deployment is allowed to contain. Admission policy does — it's the enforcement point for Pod Security, image provenance (Ch 13), and your org's rules.

How it works

The admission chain (after authn + authz):

   1. MUTATING admission webhooks     may change the object (inject
      sidecars, set defaults, add labels). Order matters; can re-invoke.
   2. Object SCHEMA validation        the API server's own checks.
   3. VALIDATING admission webhooks   accept or reject; cannot change.
   4. VALIDATING ADMISSION POLICIES   in-tree, CEL expressions, NO webhook
      (no extra pod, no availability risk) -- GA in 1.30. Prefer these for
      simple allow/deny.
   5. persist to etcd.
   (Built-in controllers like NamespaceLifecycle, ResourceQuota,
   PodSecurity, and the LimitRanger also run in this pipeline.)

Pod Security Admission (PSA) — the built-in, replaces PodSecurityPolicy:

   Three LEVELS, applied per-namespace via labels:
     privileged  -- no restrictions.
     baseline    -- blocks known privilege escalations: no privileged, no
                    hostPath (mostly), no hostNetwork/PID/IPC, no dangerous
                    caps added, no host ports (mostly), AppArmor not
                    unconfined, etc. "Don't do the obviously bad thing."
     restricted  -- hardened: runAsNonRoot, allowPrivilegeEscalation:false,
                    drop ALL caps (NET_BIND_SERVICE allowed),
                    seccompProfile RuntimeDefault|Localhost, no hostPath,
                    volumes limited to safe types, etc.

   Three MODES per level:
     enforce  -- reject violating pods
     audit    -- allow but record in the audit log
     warn     -- allow but return a warning to the client

   Labels on the namespace:
     pod-security.kubernetes.io/enforce: restricted
     pod-security.kubernetes.io/enforce-version: v1.30
     pod-security.kubernetes.io/warn: restricted
     pod-security.kubernetes.io/audit: restricted

   Rollout pattern: set `warn` + `audit` to `restricted` cluster-wide,
   watch the audit log / warnings for breakage, fix workloads, THEN flip
   `enforce`. Exempt only specific system namespaces, by name, with a
   comment.

Policy engines (for everything PSA can't express):

   OPA GATEKEEPER   ConstraintTemplate (Rego) + Constraint CRDs. Rich, a
     large policy library, `gator` for unit-testing policies, audit mode
     that reports existing violations. Rego has a learning curve.
   KYVERNO          YAML policies, no Rego. validate / mutate / generate /
     verifyImages / cleanup. `Audit` vs `Enforce`. Policy Reports. Great
     DX; CLI `kyverno apply` / `kyverno test` for CI.
   VALIDATING ADMISSION POLICY (VAP)  in-tree, CEL, no webhook. Use for
     simple deny rules -> zero operational risk (a broken webhook can wedge
     the whole cluster; VAP can't).
   Kubewarden (WASM policies), jsPolicy (JS/TS) -- alternatives.

Policies worth enforcing (beyond PSA restricted):

   [ ] images: only from approved registries, BY DIGEST, SIGNED with the
       expected identity + required attestations (Ch 13).
   [ ] every namespace has a default-deny NetworkPolicy (generate one if
       missing -- Kyverno `generate`).
   [ ] no `serviceAccountName` outside an allowlist for the creator
       (blocks the Ch 17 path).
   [ ] resource requests/limits set (DoS / noisy-neighbour).
   [ ] `automountServiceAccountToken: false` unless annotated as needing
       the API.
   [ ] no `:latest`, no unpinned tags.
   [ ] required labels: owner, team, data-classification.
   [ ] no LoadBalancer/NodePort Services except in an allowlist.
   [ ] no `hostPath` except exact paths for named system DaemonSets.
   [ ] deny creating `ClusterRoleBinding` to cluster-admin except
       break-glass; deny `bind`/`escalate`/`impersonate` grants in new
       Roles.
   [ ] PDBs / topology spread for critical workloads (availability is a
       security property -- Ch 28).

Webhook availability = cluster availability. A validating webhook with failurePolicy: Fail that becomes unavailable blocks all matching API writes (potentially the whole cluster). Mitigations: failurePolicy: Ignore for non-critical rules (accepting a fail-open gap), tight namespaceSelector / objectSelector to scope what the webhook sees, exclude kube-system, run the webhook HA, and prefer VAP (no webhook) for critical deny rules.

Worked example

Rolling PSA restricted without an outage, then adding a signed-image gate.

   1. Label every non-system namespace:
        pod-security.kubernetes.io/warn: restricted
        pod-security.kubernetes.io/audit: restricted
      (enforce still `privileged` / unset)
   2. Deploy something; watch `kubectl` warnings and grep the audit log
      for `pod-security.kubernetes.io/audit-violations`. Typical hits:
        - container runs as root -> set runAsNonRoot + a non-root uid in
          the image
        - missing seccompProfile -> add `RuntimeDefault`
        - caps not dropped -> `drop: ["ALL"]`
        - writes to `/` -> `readOnlyRootFilesystem: true` + emptyDir /tmp
   3. Fix the workloads (usually a shared base manifest / Helm values).
   4. Flip `enforce: restricted` per namespace once its audit log is
      clean. Exempt `kube-system` and the CNI/CSI namespaces BY NAME with
      a comment; work to shrink that list.
   5. Add Kyverno `verifyImages` (Ch 13) in `Audit`, watch the Policy
      Report for images that wouldn't pass, migrate them through the
      paved-road pipeline, then flip to `Enforce`.
   6. Add a VAP (CEL) for the simple stuff so it can't be a webhook
      outage:
        validations:
          - expression: "object.spec.template.spec.containers.all(c,
              !c.image.contains(':latest') && c.image.contains('@sha256:'))"
            message: "images must be pinned by digest"

Practice (75 min) — kind cluster

  1. Apply PSA warn+audit: restricted cluster-wide. Deploy a few real charts. Collect every violation. Fix a workload to pass restricted. Flip enforce on its namespace.
  2. Install Kyverno. Write and test (with kyverno test) policies for: approved-registries + digest-pinned; default-deny NetworkPolicy generation; serviceAccountName allowlist; required owner label. Run in Audit, read the Policy Report, then Enforce.
  3. Do the same registry/digest rule as a ValidatingAdmissionPolicy (CEL). Compare: no pod, no availability risk.
  4. Break a webhook on purpose: scale Kyverno to 0 with failurePolicy: Fail on a broad rule — watch API writes start failing. Set a tight namespaceSelector and failurePolicy: Ignore for non-critical rules; observe the trade-off.
  5. Run gator verify on a Gatekeeper policy library, or Polaris / kubescape for a policy gap report.

Common mistakes

  • PSA enforce flipped cluster-wide with no audit/warn soak. Instant outage for every non-conformant workload.
  • Exempting whole namespaces "temporarily". The exemption list becomes permanent. Exempt by exact name, with a comment and a ticket to remove.
  • Policy engine stuck in Audit forever. Attackers don't read Policy Reports. Get to Enforce.
  • One giant Fail-policy webhook selecting everything. A single point of failure for the whole control plane. Scope it; use VAP for critical deny.
  • Mutating webhooks that add privileges (a sidecar with broad RBAC, a privileged init container) — now the webhook is an escalation vector.
  • Not covering all pod-spec paths (initContainers, ephemeralContainers, Deployment/DaemonSet/CronJob templates) — attackers use the uncovered field.
  • Rego/CEL bugs that fail open. Unit-test policies (kyverno test, gator) in CI like any code.

Check yourself

  1. Give the order of the admission chain and where mutation vs rejection happens.
  2. Name the three PSA levels and the three modes, and the safe rollout order.
  3. When would you choose a ValidatingAdmissionPolicy over a webhook-based policy?
  4. Why is failurePolicy: Fail on a broad webhook an availability risk, and name two mitigations.
  5. List five admission policies worth enforcing beyond PSA restricted.
  6. Why must a policy cover initContainers and workload templates, not just Pod?

Further reading

  • Docs: Kubernetes "Pod Security Admission", "Admission Controllers Reference", "Validating Admission Policy", "Dynamic Admission Control".
  • Engines: OPA Gatekeeper (+ the gatekeeper-library), Kyverno (+ the Kyverno policy library and kyverno-json), Kubewarden.
  • Guidance: NSA/CISA Kubernetes Hardening; CIS Benchmark policy sections; "Pod Security Admission migration from PSP" (Kubernetes blog).
  • Talks: "The Path to Pod Security" and "Policy as Code with Kyverno/OPA" from KubeCon.

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36