Policy as code replaces “the architecture review board said no public S3 buckets” with a rule that runs on every plan and fails the build. The rule is version controlled, testable, and applies uniformly — including to the people who wrote it.

Why review boards do not scale

A human review gate has a fixed throughput and a variable quality. As change volume grows, one of two things happens: the gate becomes the bottleneck and teams route around it, or reviewers start rubber-stamping. Both outcomes leave you with the cost of the process and none of the protection.

flowchart LR
    subgraph "Manual gate"
    A1[Change] --> A2[Queue for review] --> A3[Human judgement]
    A3 --> A4["Inconsistent · slow ·<br/>scales with reviewer headcount"]
    end
    subgraph "Policy as code"
    B1[Change] --> B2[Automated evaluation<br/>seconds]
    B2 --> B3["Consistent · immediate ·<br/>scales infinitely"]
    B3 --> B4["Humans review the policies,<br/>not every change"]
    end

The shift is that humans move from reviewing instances to reviewing rules. That is a much smaller, much higher-leverage workload.

Where policies run

Policy should be enforced at multiple points, because each catches a different escape route.

flowchart TD
    A[Developer's editor<br/>pre-commit hook] -->|fastest feedback,<br/>bypassable| B[Pull request<br/>policy check on the plan]
    B -->|blocks merge| C[Deployment gate<br/>re-evaluate before apply]
    C -->|blocks apply| D[Admission controller<br/>at the cluster/cloud API]
    D -->|blocks creation| E[Continuous audit<br/>scan running resources]
    E -->|detects what slipped through| F[Alert or auto-remediate]

The layers are not redundant. Pre-commit is fast but bypassable; pull-request checks are authoritative for the pipeline path; admission control catches anything created outside the pipeline; continuous audit catches what existed before the policy did.

What to write policies about

Start with rules that are unambiguous, objectively checkable, and materially consequential.

DomainExample rules
Network exposureNo security group allows 0.0.0.0/0 on admin ports; no storage bucket is publicly readable
EncryptionAll volumes, buckets, and managed databases encrypted at rest; TLS required in transit
IdentityNo wildcard actions in IAM policies; no long-lived access keys; no privileged service accounts in default namespaces
CostInstance types restricted to an approved list; no untagged resources; storage lifecycle rules required
AvailabilityProduction databases have backups enabled and multi-AZ; deletion protection on stateful resources
Tagging/ownershipEvery resource carries owner, cost-centre, and environment tags

Ownership tagging looks trivial next to encryption but is often the highest-value policy in the list, because everything else — cost attribution, incident routing, drift ownership, decommissioning — depends on knowing who owns a resource.

Severity levels and enforcement modes

A policy set with one severity level is unusable: either everything blocks and teams are stuck, or nothing blocks and nothing changes.

ModeBehaviourUse for
AdvisoryReports, never blocksNew policies during rollout; style preferences
Soft-failBlocks unless explicitly acknowledged with a reasonRules with legitimate exceptions
Hard-failBlocks unconditionallySecurity and regulatory requirements

The standard rollout sequence for any new policy is advisory → soft-fail → hard-fail, with a published date for each transition. Introducing a hard-fail policy against an existing estate breaks every pipeline at once and burns the credibility of the whole programme.

Exceptions must exist, and must expire

Every policy will eventually meet a legitimate exception. If there is no mechanism for one, teams will disable the check entirely — the worst possible outcome, because you lose the rule and the visibility.

A workable exception record:

  exception:
  policy: "storage-must-not-be-public"
  resource: "s3://public-docs-site-assets"
  justification: "Static marketing site assets, no customer data. Reviewed by security 2026-07-14."
  approver: "security-team"
  expires: "2027-01-14"
  ticket: "SEC-4821"
  

The expires field is the one that matters. Permanent exceptions become invisible policy holes. An expiring exception forces periodic re-justification, and expiry should re-enable the block automatically rather than merely notifying someone.

Policies are code and need tests

A policy with a bug is worse than no policy: a false negative gives false assurance, and a false positive blocks legitimate work and destroys trust in the system.

Every policy should ship with test cases in both directions:

  policy: no-public-storage-buckets
  should FAIL:
    - bucket with public-read ACL
    - bucket with policy allowing Principal "*" on GetObject
    - bucket with public access block disabled
  should PASS:
    - private bucket with public access block enabled
    - bucket with a policy scoped to a specific account principal
    - bucket fronted by a CDN using origin access identity
  

The pass cases matter as much as the fail cases. A policy that blocks correct configurations will be worked around within a week.

Making failures actionable

A policy failure should tell the engineer what is wrong, why the rule exists, and how to fix it. Compare:

DENY: rule aws-s3-002 violated

with:

Policy failed: storage bucket must not be publicly accessible Resource: aws_s3_bucket.reports The bucket’s public access block is disabled, which allows a future ACL change to expose its contents. Customer data must not be reachable without authentication (control: data-protection-3). Fix: add aws_s3_bucket_public_access_block with all four settings set to true. Example: modules/private-data-bucket. Exception: if this bucket genuinely serves public content, request an exception via SEC intake.

The second version resolves itself. The first generates a support ticket.

Continuous audit of what already exists

Pipeline policies only govern changes going forward. The existing estate needs a separate scan, because it was built before the policy existed and no plan will ever evaluate it.

Run policy evaluation against live resources on a schedule, and treat the result as a backlog with an owner per finding rather than a report nobody reads. This is also the mechanism that produces continuous compliance evidence — see Compliance as Code.

Adoption checklist

  • Policies are version-controlled, reviewed, and tested with pass and fail cases.
  • Enforcement happens at plan time, deploy time, and admission time.
  • Every new policy rolls out advisory → soft-fail → hard-fail on a published schedule.
  • An exception process exists, is used, and every exception has an expiry date.
  • Expired exceptions automatically re-enable enforcement.
  • Failure messages state the rule, the rationale, and the fix.
  • Existing resources are scanned continuously, not just new changes.

Last updated 19 Aug 2026, 00:00 UTC. history