How does policy as code work?
Every policy as code system, regardless of vendor or language, has the same four parts:
- Policy: The rule itself, written in a language like Rego, Sentinel, Cedar, or YAML. It lives in Git, gets code review, and has tests.
- Input: Structured data describing the thing being evaluated: a Terraform plan in JSON, a Kubernetes manifest, an API request, or a cloud resource configuration.
- Decision engine: The program that evaluates the policy against the input and returns a decision such as allow, deny, or a list of violations.
- Enforcement point: The place that acts on the decision: a CI job that fails the build, an admission controller that rejects a pod, or an application that denies a request.
Separating the decision from the enforcement is the design choice that makes policy as code scale. The same policy can be evaluated in a pull request, at deploy time, and against live infrastructure, and every evaluation produces the same answer for the same input. That consistency is what a manual review cannot provide.
What is the difference between policy as code and compliance as code?
The terms get used interchangeably, but they sit at different levels. Policy as code is the technique. Compliance as code is the program that uses the technique to meet framework requirements and prove it. The compliance as code guide covers the program side in depth.
| Dimension | Policy as code | Compliance as code |
|---|---|---|
| Scope | A single rule and its evaluation | A control set mapped to one or more frameworks |
| Output | A decision: allow, deny, or violations | Control status, evidence, and audit-ready reporting |
| Primary users | Platform, security, and DevOps engineers | GRC engineers, auditors, and leadership |
| Example | A Rego rule that denies public S3 buckets | That rule mapped to SC-7 and CC6.1, with decisions retained as evidence |
| Used outside compliance? | Yes: cost, reliability, and authorization rules | No: its purpose is meeting obligations |
What are the most common policy as code tools?
Most teams pick one general-purpose engine and add specialized tools where they already fit the workflow.
| Tool | Language | Typical use |
|---|---|---|
| Open Policy Agent (OPA) | Rego | General-purpose engine for any JSON input; a CNCF graduated project |
| Conftest | Rego | Runs OPA policies against config files such as Terraform plans and Kubernetes YAML |
| HashiCorp Sentinel | Sentinel | Policy checks between plan and apply in HCP Terraform and Terraform Enterprise |
| AWS Config rules | Managed rules, Lambda, or CloudFormation Guard | Continuous evaluation of live AWS resource configurations |
| Cedar | Cedar | Application authorization decisions; open source from AWS |
| Kyverno | YAML | Kubernetes admission control and policy reporting |
| Checkov | Python or YAML | Static scanning of IaC files against built-in and custom checks |
For how these tools fit into a scanning pipeline, see IaC compliance scanning.
What is an example of policy as code?
The examples below all enforce variations of one intent: storage must not be publicly accessible, which maps to boundary protection (SC-7) in NIST SP 800-53.
OPA and Rego against a Terraform plan
This policy reads the JSON output of terraform show -json and returns a violation for any public access block that leaves public ACLs allowed:
package terraform.s3
import rego.v1
deny contains msg if {
some rc in input.resource_changes
rc.type == "aws_s3_bucket_public_access_block"
rc.change.after.block_public_acls != true
msg := sprintf("%s must set block_public_acls = true (SC-7)", [rc.address])
}Sentinel in HCP Terraform
Sentinel evaluates the same plan inside the Terraform run itself. The main rule must be true for the run to proceed:
import "tfplan/v2" as tfplan
public_access_blocks = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket_public_access_block" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
main = rule {
all public_access_blocks as _, pab {
pab.change.after.block_public_acls is true and
pab.change.after.restrict_public_buckets is true
}
}AWS Config managed rule, deployed with Terraform
The two policies above are preventive. This AWS Config rule is detective: it continuously evaluates existing buckets, including ones changed outside Terraform. It requires an AWS Config configuration recorder in the account.
resource "aws_config_config_rule" "s3_public_read_prohibited" {
name = "s3-bucket-public-read-prohibited"
description = "SC-7: S3 buckets must not allow public read access"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"
}
}Cedar for application authorization
Policy as code also covers who can do what inside an application. This Cedar policy lets auditors read evidence and blocks deletion by anyone outside an admin group:
permit (
principal in Group::"auditors",
action == Action::"readEvidence",
resource in Folder::"soc2-2026"
);
forbid (
principal,
action == Action::"deleteEvidence",
resource
)
unless { principal in Group::"evidence-admins" };How do you implement policy as code in a GRC program?
Start with a control that already causes pain in audits and has clean input data. Encryption, public access, and logging settings are common first choices. Then work through a short loop:
- Write the rule and tag it with the control IDs it supports, such as SC-7 or CC6.1.
- Write unit tests with passing and failing inputs (
opa testfor Rego) so the policy itself is verified. - Run it in CI in warn-only mode, fix existing violations, then switch it to blocking.
- Add a detective rule against live resources to catch drift and manual changes.
- Retain every decision with a timestamp so it can serve as audit evidence.
- Define an exception process with an owner and an expiry date, recorded in code or in your risk register.
A minimal CI step using Conftest looks like this:
- name: Policy check
run: |
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test --policy policy/ --namespace terraform.s3 plan.jsonPolicy files make excellent audit artifacts because an auditor can read the exact logic that enforces a control. How auditors evaluate that logic is covered in auditing infrastructure as code, and the broader automation roadmap is in GRC automation. For the controls these policies enforce, see what a security control is.
Frequently Asked Questions
What is policy as code?
Policy as code is the practice of writing rules (security, compliance, operational, or authorization) in a machine-readable language, storing them in version control, and having a policy engine evaluate them automatically. Common engines include Open Policy Agent, HashiCorp Sentinel, AWS Config, Cedar, and Kyverno.
What is the difference between policy as code and compliance as code?
Policy as code is the technique: a rule, an engine, and a decision. Compliance as code is the program built on that technique, which maps policies to framework controls, collects the decisions as evidence, and reports posture to auditors and leadership. You can do policy as code without a compliance program, but compliance as code depends on it.
What language does Open Policy Agent use?
Open Policy Agent uses Rego, a declarative query language designed for evaluating structured data such as JSON. A Rego policy describes conditions that should produce a decision, like a deny message, and OPA evaluates those conditions against whatever input you provide, from a Terraform plan to a Kubernetes manifest.
Is AWS Config policy as code?
Yes. AWS Config rules evaluate resource configurations against a desired state and record each resource as compliant or noncompliant. Managed rules are predefined by AWS, and custom rules can be written as Lambda functions or in CloudFormation Guard syntax. Deploying those rules through Terraform or CloudFormation keeps them versioned like any other code.
Where should policy as code run?
Ideally in more than one place. Run it in CI to block noncompliant infrastructure before deployment (preventive), at admission or authorization time to stop bad requests (preventive), and against live resources to catch drift (detective). Each layer catches problems the others miss.
Do GRC professionals need to write policy as code?
GRC engineers increasingly do, because translating a control objective into a precise rule requires compliance judgment as well as coding skill. Even GRC analysts who do not write policies benefit from reading them, since a policy file is often the clearest documentation of how a control actually works.