Policy as Code
Encoding organisational rules — no public buckets, required tags, resource limits, deployment constraints — as machine-evaluated checks that run on every change.
The question, the obvious approach, and why it breaks
Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.
How does a rule that exists in a document become a rule that is actually true of production?
Organisational rules are written down and then enforced by memory, review and periodic audit — which means their compliance rate is unknown and their violations are discovered long after they were introduced.
Document the standards, cover them in review, and run a quarterly audit to catch anything that slipped through.
Review catches what the reviewer happens to look for. Nobody reads a two-hundred-line infrastructure diff hunting for a missing tag.
- Review catches what the reviewer happens to look for. Nobody reads a two-hundred-line infrastructure diff hunting for a missing tag.
- A quarterly audit finds violations up to three months after they were introduced, by which point something depends on the violating resource.
- Audits produce a list of exceptions and no mechanism, so the same violations reappear before the next audit.
- Rules stated in prose are ambiguous at the edges, and the edges are where violations live. "Buckets should not be public" does not say what to do about a bucket serving static assets.
- Nobody can answer "are we compliant right now" without commissioning work.
What is actually happening
Underneath the tooling, which is the part that survives a change of tool.
- Writing the rule as code forces it to become precise. Most of the value arrives before enforcement does — the act of expressing "no public buckets" executably surfaces the exceptions the prose was hiding.
- A policy engine evaluates a rule against a structured description of a proposed or existing resource, and returns a decision with a reason. That is the whole model; the rest is where it runs.
- It must run in more than one place, because the places have different properties. Early evaluation is fast feedback and can be bypassed; late evaluation is enforcement and is discovered too late to be pleasant. Both, on the same rule, is the working pattern (Guardrails, Not Gates).
- Continuous evaluation against live state is the third mode and the one most often missing. Change-time evaluation cannot see resources created before the policy existed, or changed by a route the policy does not cover.
- The exception mechanism is part of the policy system, not a workaround for it. Exceptions with an owner and an expiry are policy; exceptions in a spreadsheet are erosion.
Four rules that cover most of the value
Almost every organisation converges on the same small set of rules first, because they correspond to the failures that actually happen: something became publicly reachable, something cannot be attributed, something consumed more than its share, something reached production by an unintended route.
| Rule class | Concrete example | What it prevents | What it replaced |
|---|---|---|---|
| Exposure | No storage bucket with public read; no database with a public address | Data reachable by anyone who guesses a name (Public Exposure, Read With Context) | A security review of every infrastructure change |
| Attribution | Every resource carries owner, environment and cost centre | Resources nobody claims and spend nobody can allocate (Cost Drivers) | A quarterly hunt for the owner of an expensive instance |
| Resource envelope | Every workload declares requests and limits, within a per-environment ceiling | One workload starving its neighbours; a loop provisioning a hundred instances (Requests and Limits) | Reviewing manifests by eye for missing fields |
| Deployment constraints | Images by digest from approved registries only; signed artifacts; no latest | Deploying something other than what was built and reviewed (Tags Versus Digests) | Trusting that everyone remembers the convention |
The rule is one artifact, evaluated in three places
A rule that runs in only one place is either too late to be pleasant or too early to be binding. The pattern that works is one definition evaluated at three points with different jobs.
The third point is the one teams skip, and it is the one that answers the question an auditor actually asks: not "do we check new changes" but "is production compliant right now".
1rule "no-public-object-storage"2 severity: deny3 applies_to: object_storage_bucket4 5 deny when:6 resource.public_access == true7 and not exception_active(resource.id)8 9 message:10 "Bucket {resource.id} allows public read.11 Serve public assets through the CDN path instead: <runbook link>.12 If this genuinely must be public, request a scoped exception13 with an owner and an expiry: <exception link>."14 15 evaluated_at:16 - developer_machine # advisory, instant, bypassable17 - change_time # blocking, on the plan, before anything exists18 - admission # blocking, at the API, so the pipeline is not the only path19 - continuously # against live state, to catch what predates the ruleWritten tool-neutrally on purpose — the mechanism is identical across engines and the syntax is not. The parts that matter are the ones people leave out: an actionable message, a modelled exception check inside the rule itself, and evaluation against live state as well as against changes.
Rolling out a rule against a fleet that already violates it
The hard part of policy is never the first environment. It is the fifty existing resources that predate the rule, and the fact that enforcing on day one blocks work that has nothing to do with the violation.
- 1Write it warn-only
Evaluate and report; block nothing.
fails by Enforcing immediately and halting unrelated changes across every team.
evidence A report of what would have been blocked, with owners attached.
- 2Measure the existing violations
Run against live state, not just against new changes.
fails by Only seeing changes, so the pre-existing fleet stays invisible.
evidence A violation count and a list, per owning team.
- 3Check the false positives
Confirm the rule allows what it should allow.
fails by Shipping a rule whose edges nobody tested, then training teams to bypass it.
evidence Policy tests covering allowed cases, not only denied ones.
- 4Remediate or except
Fix violations; model the genuine exceptions with owner and expiry.
fails by Blanket-excepting everything that currently fails, which is the rule not existing.
evidence Violation count trending to zero plus a short, dated exception list.
- 5Enforce at change time
Block new violations at plan or pipeline stage.
fails by Enforcing while remediation is incomplete, so teams are blocked by someone else's debt.
evidence A deliberately bad change is refused with the rule name.
- 6Enforce at the API
Same rule at admission, so bypassing the pipeline does not bypass the rule.
fails by Pipeline-only enforcement, which holds for the compliant and not for the hurried (Manual Production Changes).
evidence The same bad change is refused when applied directly.
- 7Keep scanning
Continuous evaluation catches drift and anything created by an unmodelled route.
fails by Assuming change-time enforcement means the fleet stays compliant.
evidence Scheduled scan result is a number on a dashboard, and it is zero.
Steps two and four are the ones that get skipped, and skipping them is why policy programmes stall: the rule is enforced for new work while the old violations remain, so the fleet is permanently split into what the policy covers and what it politely ignores.
How to do it properly
Most important first.
- Start with the rules that have already caused an incident or an audit finding. Policy written speculatively mostly generates noise.
- Ship every new rule in warn-only mode first. Report what it would have blocked, fix the existing violations, then enforce (Change Management).
- Evaluate the same rule at development time, at change time and continuously against live state, from one definition.
- Make refusals actionable: what rule, what resource, what specifically is wrong, and what a compliant version looks like. A denial with a rule identifier and nothing else is a support ticket in the making.
- Model exceptions explicitly with an owner, a reason and an expiry date, and re-evaluate them on expiry.
- Version and test policies like any other code, including tests for the cases that must be *allowed* (Required Checks).
How much can this affect
Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.
Policy sits on the change path for every team, so a bad rule stops all delivery at once. Contained by warn-only rollout, versioned policy releases and an override that is loud, time-boxed and recorded.
What can go wrong
- Policy that only evaluates changes, leaving pre-existing violations invisible forever.
- Policy enforced in the pipeline while the underlying cloud or cluster API is still directly reachable, so the rule holds only for people who use the front door (Manual Production Changes).
- Rules that are too broad, blocking legitimate work and training everybody to reach for the exception path.
- A permanently-growing exception list, which is the same as not having the policy but with more machinery.
- Policy failures that fail open: the evaluation service is down, so everything is allowed and nobody notices.
- Policy as the only control, with no blast-radius limit behind it — everything the rules did not anticipate proceeds at full speed.
- "Policy as code means security is handled." It means a specific list of encoded rules is enforced. Everything not on the list is exactly as unenforced as before (The Delivery Chain as Attack Surface).
- "If it passed policy, it is fine." Policy is necessary-not-sufficient by construction. It is a floor, not a verdict.
- "Enforce everything from day one." Enforcing against a fleet with pre-existing violations blocks unrelated work at the worst moment. Warn, remediate, then enforce.
- "An exception means the policy failed." A modelled exception with an owner and an expiry is the policy working. An unmodelled one is the failure.
Operating it
Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.
- A deliberately non-compliant change is refused in the pipeline, with a message naming the rule.
- The same change is also refused if applied directly to the platform API, bypassing the pipeline.
- A continuous scan reports the current violation count against live state, and it is a number someone looks at.
- Every active exception has an owner and a future expiry date.
- A rule that blocks legitimate work is reverted to warn-only immediately — this should be a one-line change that anyone on the platform team can ship in minutes.
- Because policy sits on the change path for everything, a bad policy release has the blast radius of "nobody can deploy". Roll policy out the way you roll out a service: versioned, staged, and reversible (Rollback: Only Useful If It Is Actually Safe).
- A failing policy engine must fail closed for genuinely dangerous actions and fail open for advisory ones — decide which is which before the outage, not during it.
- Automate evaluation, reporting, exception expiry and the drift scan against live state.
- Automate the generation of compliance evidence from the same rules, so audit is a query rather than a project (The Audit Trail).
- Do not automate the granting of exceptions. That is the one place a human should be reading the reason.
- Do not treat a passing policy set as proof of security. It proves the encoded rules hold, which is a much narrower claim (Scanning, and Why a Finding Is Not a Risk).
- Policy is a system with its own availability, latency and failure modes, sitting on the critical path of every change.
- Precision costs effort. Getting a rule right at the edges is often more work than the rule itself.
- Enforcement in multiple places means the same rule exists in multiple runtimes, and keeping them in agreement is ongoing work.
Where this applies
This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.
- ORG-SPECIFICWhich rules exist is a local decision driven by regulatory obligations, past incidents and risk appetite. An organisation handling card data has mandatory rules about network exposure that a hobby project has no reason to encode; copying someone else's policy set produces refusals nobody can explain.
- TOOL-SPECIFICWhere a rule can be enforced depends on the platform. Kubernetes exposes admission control, which lets the same rule run at the API. A managed PaaS may expose no enforcement point at all, in which case the pipeline is the only control and direct console changes are an accepted gap to detect after the fact rather than prevent.
Where the depth lives
This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.