In Depth · Policy-as-Code Philosophy

Policy-as-Code Philosophy

Nine project certifications sit on this course's shelf, and exactly one of them touches policy: KCA, tested against Kyverno in exhaustive YAML detail. That narrowness is deliberate — and it leaves a gap this page exists to close. Every policy engine guarding a Kubernetes cluster, whatever badge is printed on it, is answering the same design question — reject the request outright, quietly rewrite it, or spawn a companion object in response — and answering it at exactly one moment, before anything is ever written to etcd. This page steps back from Kyverno's specific fields to the model underneath it: the admission-control mechanics every engine shares, why a policy earns the same scrutiny as production code, and the genuine philosophical fork between Kyverno's Kubernetes-shaped YAML and OPA/Gatekeeper's general-purpose Rego. The fork matters past KCA — it's the difference between a platform that runs one guardrail tool by accident and one that chose it on purpose.

☺ Explain it like I'm 10

Picture two ways to keep the wrong kids out of a clubhouse. Way one: carve the rule right into the door — a slot shaped exactly like the correct membership card, so the door only opens if what you're holding matches the outline. Fast to build and easy to read, but that carved slot only ever works on that one door. Way two: post a guard who has memorized general rules about permission and can explain them out loud. Training the guard takes longer, but the same guard can walk over and check a different door, a different building, even a delivery truck, because the rule lives in the guard's head, not cut into any one doorframe. Kyverno is the carved door. OPA and Gatekeeper are the guard — a general brain that has to be specially told what a "Pod" even is before it can check one at all.

🐢Your host for this topic: Timmy the Turtle — the guardrail. Timmy insists every new rule prove itself against a deliberately bad object before anyone trusts it, and honestly cares less which engine you picked than whether you can say out loud why you picked it.

One doorway, two verbs: the admission-control model

☺ Like you're 10: The cluster's front door has two little windows in it — one where a guard can hand back your form with something already filled in, and one where a guard just says yes or no. Everything passes both windows, in that order, before it's let inside.

Before any policy engine gets a say, ordinary Kubernetes authentication and authorization already ran — is this identity real, and is it allowed to attempt this verb on this resource at all? That's RBAC territory, covered at real depth by the sibling Kubernetes course's KCSA and CKS, and this course assumes it rather than re-teaching it. What a policy engine adds is a second gate, sitting after authorization and before persistence, and Kubernetes splits that gate into exactly two phases every object passes through in order. Mutating admission runs first and may rewrite the object — inject a default, add a label, patch in a missing field. Validating admission runs second, sees whatever the mutating phase already changed, and returns a final verdict: allowed, or rejected with a message. A rejection at either phase means the object is never written to etcd at all — there's nothing to roll back, because nothing happened.

Request kubectl · GitOps API server authn then authz WINDOW ONE Mutating admission Kyverno mutate Gatekeeper Assign native MutatingAdmissionPolicy schema validation WINDOW TWO Validating admission Kyverno validate Gatekeeper violation native ValidatingAdmissionPolicy Persisted to etcd admitted Rejected nothing written the two windows are the same for every engine — only what runs inside them differs
◆ Key idea

Kyverno, OPA/Gatekeeper, and Kubernetes' own CEL-native ValidatingAdmissionPolicy are three different engines that can occupy the exact same two slots in the exact same request path. None of them changes when a policy runs. What differs between them is entirely how the rule inside them gets written — and that difference is the rest of this page.

Why the rule belongs in a pull request, not a person's memory

☺ Like you're 10: A rule that only lives in one person's head has exactly one point of failure — that person being on vacation, in a different meeting, or simply wrong that day.

A policy engine doesn't just enforce a rule — it encodes, in a form a machine will obey unsupervised, forever, a claim about what's dangerous. That claim deserves at least the scrutiny given to the application code it governs, and arguably more, because a mistake in it fails in one of two expensive directions: too loose, and it becomes a false sense of safety nobody notices until an incident; too strict, and it becomes an outage that looks like the platform itself broke. Writing the rule as code and putting it in Git is what makes that scrutiny actually happen instead of remaining a good intention. Git gives a policy change a diff a reviewer can read in thirty seconds instead of a change nobody saw coming; it gives it attribution — who decided privileged containers were fine in this one namespace, and when; and it gives it a revert as cheap as reverting any other commit, instead of someone manually reconstructing what the rule used to say. Both engines this page compares ship a real test runner for exactly this reason — kyverno test and opa test both let a rule prove, in CI, that it still catches what it claims to catch before it's ever trusted against a live cluster.

The other half of the discipline is what happens once a policy CRD is merged: it's just another Kubernetes object, so a GitOps controller reconciles it exactly the way it reconciles a Deployment — drift back to what Git says, on the same schedule, with the same audit trail. A policy applied once by hand from a laptop and never touched again isn't policy as code; it's a screenshot of a good intention that Git never got to see.

⚠ The exception that skips its own rule

Every mature policy program eventually needs exceptions — a workload that genuinely can't comply yet, a migration in progress. Both engines model this as its own reviewable object: a Kyverno PolicyException, a narrowly scoped Gatekeeper Constraint exclusion. The discipline only holds if the exception goes through the same pull request and review as the policy itself. An exception granted in a Slack thread, applied by hand to unblock someone's Friday deploy, is the exact failure mode this whole section exists to prevent — just one layer further out.

Two philosophies wearing the same badge

☺ Like you're 10: The carved door and the memorizing guard from the analogy above are a real engineering choice, not just a story — and most platforms only ever build one of them.

Kyverno's premise is that policy is a Kubernetes resource, full stop. A ClusterPolicy is ordinary YAML that reads almost like the object it's checking, carrying four built-in verbs — validate, mutate, generate, verifyImages — directly in its schema. A team already fluent in Kubernetes manifests can read a Kyverno policy on sight, because it was never asked to learn a second language to do it. The cost of that fluency is scope: the policy language has no meaning outside an admission review. Ask it to also gate a Terraform plan or a CI artifact and there's simply nothing there to ask.

OPA and Gatekeeper start from the opposite premise: policy is a general-purpose brain, and Kubernetes is just one thing it's been taught to look at. Rego is a real, standalone declarative language that evaluates arbitrary JSON — it has no built-in notion of a "Pod" until a ConstraintTemplate tells it where to look inside an admission review's input. Gatekeeper is the Kubernetes-shaped wrapper around that brain — the webhook, the audit loop, the CRD generation — but the brain itself was never Kubernetes-specific, and the exact same Rego package can evaluate a Terraform plan in CI, or a policy check inside a service mesh, with zero code shared with the Kubernetes case beyond the rule text.

#  KYVERNO — the rule is shaped exactly like the object it checks
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
spec:
  background: true
  rules:
    - name: no-privileged
      match:
        any:
          - resources: { kinds: [Pod] }
      validate:
        failureAction: Enforce
        message: "Privileged containers are not allowed."
        pattern:
          spec:
            containers:
              - =(securityContext):
                  =(privileged): "false"
# OPA / GATEKEEPER — the same idea, in a language that has never
# heard of a "Pod" until this rule tells it where to look
package k8sdisallowprivileged

violation[{"msg": msg}] {
	c := input.review.object.spec.containers[_]
	c.securityContext.privileged == true
	msg := sprintf("privileged containers are not allowed: %v", [c.name])
}
# The identical Rego engine, evaluating something that has never heard of a Pod:
opa eval -i terraform-plan.json -d policy/ "data.terraform.s3.deny"

# Kyverno has no equivalent invocation. Its only input shape is a Kubernetes
# admission review — the language was never designed to be general-purpose.
Kyverno — policy is Kubernetes ClusterPolicy validate · mutate generate · verifyImages Kubernetes admission review the only caller — by design OPA + Gatekeeper — policy is a brain Rego policy package no Kubernetes concept built in Gatekeeper K8s admission review Conftest / opa eval a Terraform plan, in CI Service mesh authz hook same Rego file, three callers — none of them Kubernetes-specific by design
DimensionKyvernoOPA + Gatekeeper
Rule languageOrdinary Kubernetes YAML — a ClusterPolicy/Policy resourceRego, a standalone declarative language
What the engine natively understandsKubernetes objects onlyArbitrary JSON — Kubernetes is one caller among many
Portable beyond a clusterNo — meaningless outside an admission reviewYes — the same Rego gates CI, Terraform, a mesh
Mutation, as a first-class verbYes — mutate, in the same schemaYes, via a separate Assign/AssignMetadata object
Generating a companion resourceYes — generate (e.g. a default-deny NetworkPolicy)Not native — needs custom controller logic on top
Verifying image signaturesYes — verifyImages is a built-in rule typeNot native — usually a separate signing-policy chain
Ramp for a YAML-fluent Kubernetes teamLow — a policy reads like the object it checksReal — Rego is its own language and mental model
What this course testsKCA, in exhaustive depthNot on this course's shelf — see Platform Engineering's tool page

Enforcement is a dial, not a switch — and that's part of the philosophy too

☺ Like you're 10: Turning a brand-new rule fully on the moment you write it is how you find out, the hard way, whose deploy it was quietly breaking all along.

Both philosophies converge on identical operational wisdom despite their different syntax. Kyverno expresses it per rule as failureAction: Audit versus Enforce; Gatekeeper expresses the same idea per constraint as enforcementAction: dryrun, warn, or deny. In every case, the honest first move for a new rule is to let it run and simply record what it would have blocked, before it blocks anything at all — because a cluster that's been running unpoliced for months almost always has existing objects the new rule would reject on sight, and finding that out by breaking someone's Tuesday deploy is a worse way to learn it than reading a report. Rolling from audit to enforcement is itself a change worth the same discipline as section two of this page: a pull request that flips one rule's mode, reviewed and merged like any other diff — not a toggle someone flips from a dashboard because a Friday incident made the room nervous.

🐢 Try it

Take any validate rule and deliberately ship it in audit mode first. Point it at a cluster with real workloads, wait one scan cycle, and read what it would have rejected before you ever flip it to enforce — the write-an-enforcing-policy drill walks through exactly this rollout, end to end, on Kyverno specifically.

Choosing a philosophy — or living with someone else's

☺ Like you're 10: Most real clubhouses end up with one kind of guard, not both auditioning forever — but everyone inside should be able to say why that guard, and not the other one, is the one on duty.

Most platforms commit to one engine rather than running both indefinitely, and the honest deciding question is about your team and your estate, not which engine is "better." A team that's fluent in Kubernetes YAML and expects to stay Kubernetes-only leans toward Kyverno — lower ramp, and every rule already reads like the manifests it governs. A team that already needs, or expects to need, one policy brain across CI, cloud infrastructure, and a service mesh — see Service Mesh Architecture for how Istio's own authorization policy sits as a third, related-but-different control surface — leans toward OPA/Gatekeeper, and treats Rego's steeper ramp as a cost worth paying once, rather than three separate policy languages maintained in parallel forever. Golden Kubestronaut candidates specifically meet Kyverno in real depth through KCA, and meet the general-purpose side of this fork conceptually through the Platform Engineering course's own OPA & Gatekeeper and Kyverno tool pages — worth reading even if your own cluster only runs one of the two, because inheriting someone else's cluster eventually means reading the engine you didn't pick.

🎬 At Mission Control
🦊

Foxy: If KCA only tests Kyverno, why do I need to know OPA even exists?

🐢

Timmy the Turtle: Because the exam tests one engine's syntax. Your job, someday, will test whether you can read whichever engine you inherit — and half the clusters out there chose the other one.

👺

Gizmo: Or — hear me out — skip the pull request entirely. I'll just kubectl apply the new ClusterPolicy straight from my laptop. Saves a whole review cycle! 🤑

🐢

Timmy the Turtle: That's not a shortcut, Gizmo, that's the exact failure mode a policy-in-a-pull-request exists to prevent — a rule nobody else read, that nobody can diff, that GitOps will fight you over the moment it reconciles against a Git state that never saw your change.

🤖

Recon the Robot: BEEP. Confirmed. Apply a policy by hand and I'll quietly revert it on the next sync, because Git — not your laptop — is what I trust.

🦉

Professor Owl: Which is really the whole page in one exchange. The doorway is the same for everyone. What you write to guard it, and where that writing lives, is the choice that actually matters.

🐢 Timmy's checkpoint

1. Name the two phases every object passes through in the Kubernetes admission-control model, and say which one runs first. 2. Why does a policy rule deserve the same code-review discipline as application code — name two concrete things Git gives a policy change that a wiki page never can. 3. In one sentence each, describe Kyverno's core philosophy and OPA/Gatekeeper's core philosophy. 4. What can Rego plus OPA check that Kyverno structurally cannot, and why? 5. Kyverno's Audit/Enforce and Gatekeeper's dryrun/warn/deny both express the same operational idea — what is it? 6. Which certification on this course's shelf tests one of these two engines in real depth, and which engine is it?

Check your answers
  1. Mutating admission runs first and may rewrite the object; validating admission runs second and returns the final allow/deny verdict. A rejection at either phase means the object is never persisted to etcd.
  2. Any two of: a readable diff showing exactly what changed, attribution for who approved a change to what's allowed, a cheap revert if the rule turns out wrong, and a unit test (kyverno test / opa test) proving the rule catches what it claims to before it's ever trusted against a live cluster.
  3. Kyverno: policy is written as an ordinary Kubernetes resource, shaped like the object it checks, native to Kubernetes and nothing else. OPA/Gatekeeper: policy is written in a separate general-purpose language, Rego, with Gatekeeper as one Kubernetes-shaped wrapper around a brain that has no Kubernetes concept built in.
  4. Evaluate something that isn't a Kubernetes object at all — a Terraform plan, a CI artifact, a service-mesh authorization request — because Rego's input is arbitrary JSON, not a Kubernetes admission review specifically. Kyverno's only input shape is a Kubernetes admission review, so it has no equivalent invocation.
  5. Roll a new rule out observing first — record what it would block without blocking anything — measure the real blast radius against live traffic, then tighten to actually enforcing, rather than launching a brand-new rule straight into blocking mode and finding its false positives the hard way.
  6. KCA tests Kyverno, in exhaustive, field-by-field depth.