Compliance as Code
The previous page ended on a problem: least-privilege RBAC,
default-deny networks, and signed images are only safe if someone actually writes and maintains those
grants — and nothing stops an engineer from binding cluster-admin “just to make it work” at 2am. This
final page of the Part is about the mechanism that makes the rules unbreakable rather than merely
recommended: codify the policy and let the platform enforce it on every change.
This is the same move the whole book keeps making, applied to governance. IaC turned infrastructure from clicks into reviewable text. Compliance as code turns the rules about that infrastructure — “no public S3 buckets,” “every image must be signed,” “no privileged containers” — from a document in a wiki into executable policy that runs automatically and blocks violations.
Why manual audits fail
Section titled “Why manual audits fail”Traditional compliance is a periodic audit: once a quarter (or once a year, before a certification), someone manually inspects the system, fills in a spreadsheet, and attests that the rules were followed. The problem is structural, and it’s the same problem the overview raised about the end-gate.
MANUAL AUDIT COMPLIANCE AS CODE ──────────── ────────────────── checked once a quarter checked on EVERY change a snapshot — true on audit day a property — true continuously drift between audits is invisible violation blocked at the gate evidence = a spreadsheet someone evidence = the policy + its logs, filled in by hand generated automatically- It’s a snapshot, not a property. An audit proves the rules held on the day someone looked. The day after, an engineer opens a security group to the world and nobody knows until the next audit — months of exposure between checks.
- It doesn’t scale. Manually reviewing every resource in a large platform is impossible, so auditors sample. Sampling means most violations are never seen.
- The evidence is hand-made and untrustworthy. A human-filled checklist is exactly the error-prone manual artifact this book exists to eliminate.
Policy as code: OPA and Rego
Section titled “Policy as code: OPA and Rego”Policy-as-code expresses your rules as machine-evaluable code instead of prose. The most widely adopted engine is OPA (Open Policy Agent), a general-purpose policy engine, and its policy language Rego. The shape is always the same: a policy takes some input (a Kubernetes manifest, a Terraform plan, an API request) and returns a decision — allow, or deny with a reason.
# Rego (OPA policy) — deny any container that runs as privileged. Input is a Kubernetes admission review.package kubernetes.admission
deny contains msg if { some c input.request.object.spec.containers[c].securityContext.privileged == true msg := sprintf("container %v must not run as privileged", [c])}Because the rule is code, it is version-controlled, reviewed, and tested like any other code — the policy itself goes through a pull request. “What are our security rules?” stops being tribal knowledge and becomes a repository you can read, diff, and reason about.
Where it runs: admission control
Section titled “Where it runs: admission control”Policy is only as good as the gate that enforces it. In Kubernetes the gate is the admission controller: a hook in the API server that intercepts every create/update request before it’s persisted, runs it past your policies, and rejects anything that violates them. Two popular policy controllers wire OPA-style policy into this gate:
- Gatekeeper — the OPA project’s Kubernetes admission controller; you write constraints backed by Rego.
- Kyverno — a Kubernetes-native policy engine where policies are themselves Kubernetes resources written in YAML (no separate language to learn).
kubectl apply ─► API server ─► ADMISSION CONTROLLER ─► policy eval │ │ │ ┌───────┴────────┐ │ │ allow → persist │ │ │ deny → REJECT │ ← never created └────────────┴────────────────┘This is the same admission-gate machinery the earlier pages kept pointing at: it’s how unsigned images get blocked and how unscanned images are refused. Compliance as code is the general framework; “block unsigned images” and “no privileged pods” are just two policies running on it.
Guardrails vs. gates
Section titled “Guardrails vs. gates”There’s a subtle but important design choice in how a policy intervenes, and it mirrors a recurring DevSecOps tension between safety and developer flow.
| Gate | Guardrail | |
|---|---|---|
| Behavior | Blocks the action; you must comply to proceed | Shapes/auto-corrects, or warns, and lets you continue |
| Feels like | A checkpoint — “stop, you may not” | A lane marker — “we nudged you back” |
| Best for | Hard, non-negotiable rules (no public DB, must be signed) | Best-practice defaults where exceptions are reasonable |
A gate is hard enforcement: the privileged-container policy above rejects the deploy. A guardrail is softer: it might mutate the resource to add a missing security default, or warn the developer, without halting them. Good platforms use both — gates for the rules that must never be broken, guardrails for the defaults you want by default but don’t need to litigate. The art is not making everything a hard gate, because a platform that constantly says “no” gets routed around — the same failure mode as a noisy scanner.
Continuous compliance and audit evidence
Section titled “Continuous compliance and audit evidence”The final payoff reframes the auditor’s question. With policy-as-code, compliance is no longer a state you achieve before an audit and let decay — it’s a property continuously enforced and continuously evidenced. Every admission decision is logged; the policy repository is the documentation of your controls; the gate’s rejection of a violation is itself a record that the control works.
"Prove you don't allow public databases." ────────────────────────────────────────── manual: dig through configs, screenshot a console, hope it's still true as code: here is the policy (a file), here are the admission logs showing it has blocked N attempts — the evidence generates itselfSo when an auditor asks “show me that no one can deploy an unencrypted volume,” you don’t go on an archaeology expedition — you point at the policy file and the audit log of its enforcement. The evidence is a byproduct of the enforcement, not a separate manual exercise. This is continuous compliance: the rules are true now, provably, because the gate enforced them on every change since the last one.
Under the hood — two kinds of admission webhook
Section titled “Under the hood — two kinds of admission webhook”The single “admission controller” this page treats as one gate is really two hooks the API server calls in a fixed order, and the split maps cleanly onto the gate-versus-guardrail distinction above. Kubernetes dynamic admission control has mutating admission webhooks and validating admission webhooks.
kubectl apply ─► API server ─► MUTATING webhooks ─► VALIDATING webhooks ─► persist (may CHANGE object) (may only ALLOW/DENY) = guardrails = gatesMutating webhooks run first and can rewrite the object — inject a missing securityContext, add a
sidecar, set a default label. That is a guardrail: it shapes the request and lets it continue.
Validating webhooks run after, see the (possibly mutated) object, and can only accept or reject it —
no changes. That is a gate. The ordering is deliberate: mutation happens before validation, so a
guardrail can fix a fixable problem before the hard gate judges it. Gatekeeper and Kyverno plug into these
hooks — and because Kyverno can run as either, one tool can both enforce “no privileged pods” and
auto-add a default the developer forgot.
The thread: what manual step disappears
Section titled “The thread: what manual step disappears”The manual, error-prone step compliance-as-code removes is the periodic human audit — the quarterly scramble to inspect, sample, and hand-fill a checklist that’s stale the moment it’s signed. In its place: policy expressed as reviewed code, enforced automatically at the admission gate on every single change, with the audit trail produced as a side effect.
Production gets safer because the window of exposure collapses from months between audits to zero — a violating change is rejected before it’s ever persisted, not discovered after it’s been live for a quarter. The cost is real: writing and maintaining policy, the discipline of audit-then-enforce rollouts, and the judgment to keep gates rare and guardrails plentiful so the platform helps rather than obstructs. But the result is the goal the whole Part has been building toward — security and governance that move at the speed of delivery instead of fighting it.
That completes the security layer. With code, secrets, supply chain, runtime privilege, and governance all enforced by automation, you have a delivery system that’s fast and defensible. The next Part takes all of this and asks where it actually runs — the platform underneath everything, with its own economics, primitives, and failure modes: Part 9 · Cloud.
The architect’s lens
Section titled “The architect’s lens”Five questions for codifying policy:
- Why does it exist? Because least-privilege RBAC and signed-image rules are only safe if something
enforces them — nothing stops an engineer binding
cluster-adminat 2 a.m. — so you codify the policy and let the platform block violations on every change. - What problem does it solve? The periodic manual audit (a snapshot true only on audit day): policy-as-code (OPA/Rego, Gatekeeper, Kyverno) runs at the admission gate on every create/update, turning compliance from an event into a continuously-evidenced property.
- What are the trade-offs? You write and maintain policy, roll it out audit-first (a hard block on day one breaks legitimate workloads you didn’t know violated it), and balance gates (hard blocks for non-negotiable rules) against guardrails (mutating defaults) — over-gating gets routed around like a noisy scanner.
- When should I avoid it (as a hard gate)? For rules with reasonable exceptions — use a warning guardrail instead, and don’t gate on something you can’t yet remediate.
- What breaks if I remove it? The exposure window reopens from zero to months between audits — a violating change runs for a quarter before a sampled audit might catch it, and the evidence reverts to a hand-filled spreadsheet.
Check your understanding
Section titled “Check your understanding”- Why is a manual quarterly audit described as proving a snapshot rather than a property? What does policy-as-code change about that?
- Walk through what an admission controller does. Why is it the natural enforcement point for policies like “no privileged pods” or “images must be signed”?
- A new hard-blocking policy is risky to switch on. What rollout approach avoids breaking existing workloads, and why?
- Distinguish a gate from a guardrail. Why shouldn’t every rule be a hard gate, and what failure mode does over-gating share with a noisy scanner?
- Using the book’s thread, explain how compliance-as-code turns “prove you’re compliant” from an archaeology expedition into a one-line answer — and what manual step that removes.
Show answers
- An audit checks the system on the day someone looks, so it proves the rules held at that instant; any violation introduced the next day stays invisible until the next audit — months of drift. Policy-as-code checks every change at the gate, so compliance becomes a continuously-enforced property rather than a periodic snapshot.
- An admission controller is a hook in the API server that intercepts every create/update request before it’s persisted, evaluates it against your policies, and rejects violations so they’re never created. It’s the natural enforcement point because it sees every change to cluster state at the choke point — “no privileged pods” and “images must be signed” are just two policies evaluated there.
- Run the policy in audit / dry-run mode first — it reports violations without blocking — so you can find and fix or grandfather existing violators, then flip to enforce. Switching straight to blocking would reject legitimate workloads that unknowingly violate the new rule, causing an outage on day one.
- A gate hard-blocks: comply or you can’t proceed (good for non-negotiable rules). A guardrail shapes, auto-corrects, or warns while letting you continue (good for best-practice defaults with reasonable exceptions). Making everything a hard gate means the platform constantly says “no,” so people route around it — the same trust-destroying failure mode as a scanner that fails on everything.
- The policy file is the documentation of the control, and every admission decision is logged — so “prove no one can deploy a public database” is answered by pointing at the policy plus its enforcement logs, evidence generated as a byproduct. This removes the manual periodic audit (inspect, sample, hand-fill a checklist), replacing it with continuous, automatically-evidenced enforcement.