PlatformORG-SPECIFICTOOL-SPECIFIC

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.

The production question

How does a rule that exists in a document become a rule that is actually true of production?

The problem

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.

What teams do first

Document the standards, cover them in review, and run a quarterly audit to catch anything that slipped through.

How it breaks

Review catches what the reviewer happens to look for. Nobody reads a two-hundred-line infrastructure diff hunting for a missing tag.

How it breaks in production
  • 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.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

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 classConcrete exampleWhat it preventsWhat it replaced
ExposureNo storage bucket with public read; no database with a public addressData reachable by anyone who guesses a name (Public Exposure, Read With Context)A security review of every infrastructure change
AttributionEvery resource carries owner, environment and cost centreResources nobody claims and spend nobody can allocate (Cost Drivers)A quarterly hunt for the owner of an expensive instance
Resource envelopeEvery workload declares requests and limits, within a per-environment ceilingOne workload starving its neighbours; a loop provisioning a hundred instances (Requests and Limits)Reviewing manifests by eye for missing fields
Deployment constraintsImages by digest from approved registries only; signed artifacts; no latestDeploying 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".

One rule, expressed the way most policy engines express it
1rule "no-public-object-storage"
2 severity: deny
3 applies_to: object_storage_bucket
4
5 deny when:
6 resource.public_access == true
7 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 exception
13 with an owner and an expiry: <exception link>."
14
15 evaluated_at:
16 - developer_machine # advisory, instant, bypassable
17 - change_time # blocking, on the plan, before anything exists
18 - admission # blocking, at the API, so the pipeline is not the only path
19 - continuously # against live state, to catch what predates the rule

Written 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

GENERALThe sequence transfers to any rule and any engine. What varies is whether an admission-style enforcement point exists at all — where it does not, be explicit that the pipeline is a convention rather than a boundary, and lean harder on continuous scanning to detect what got around 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.

Introducing an enforced rule without stopping delivery
  1. 1
    Write 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.

  2. 2
    Measure 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.

  3. 3
    Check 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.

  4. 4
    Remediate 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.

  5. 5
    Enforce 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.

  6. 6
    Enforce 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.

  7. 7
    Keep 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.

Blast radius if this is wrongEveryone
One testEveryone
What contains it

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

Failure modes, including of the mitigation
  • 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.
Misreads this invites
  • "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'.

How you know it worked
  • 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.
How you get back
  • 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.
What to automate, and what stays human
  • 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).
What this costs
  • 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.