DevOps Peach

Security (DevSecOps) · IaC security and policy as code · Lesson 3 of 4

Policy as code with OPA and Conftest

Write your organisation’s own rules as code in Open Policy Agent’s Rego language, test Terraform plans, Kubernetes YAML or Dockerfiles with Conftest, and reuse the same approach in an admission controller.

Practice interactively →

Why custom policies

Checkov and Trivy ship generic checks. But every organisation has its own rules: "every resource must have an Owner tag", "only region ap-southeast-1 is allowed", "images must come from registry.example.com". Policy as code writes those rules as code: reviewed, versioned, tested and run automatically like application code.

Open Policy Agent and Rego

OPA (a CNCF graduated project) is a general-purpose policy engine: it takes JSON input, evaluates it against policies written in Rego, and returns a decision. The same engine serves Terraform, Kubernetes (Gatekeeper), API authorisation and CI.

# policy/tags.rego
package main

import rego.v1

deny contains msg if {
  some rc in input.resource_changes
  rc.change.actions[_] == "create"
  not rc.change.after.tags.Owner
  msg := sprintf("%s is missing the Owner tag", [rc.address])
}

Read it as: for each resource about to be created in the plan, if it has no tags.Owner, add a message to the deny set. An empty deny set means pass.

Conftest

Conftest uses OPA to test configuration files: JSON, YAML, HCL, Dockerfiles, INI and more. By default it looks for policies in the policy/ folder and rules named deny, violation and warn.

terraform show -json tfplan > tfplan.json
conftest test tfplan.json                      # uses the default policy/
conftest test -p policy/ k8s/deployment.yaml
conftest test --all-namespaces Dockerfile
conftest verify -p policy/                     # run the policy unit tests
FAIL - tfplan.json - main - aws_s3_bucket.logs is missing the Owner tag
2 tests, 1 passed, 0 warnings, 1 failure
flowchart LR
  TF["Terraform plan JSON"] --> CT["conftest test"]
  K8["Kubernetes YAML"] --> CT
  POL["policy/*.rego<br/>reviewed like code"] --> CT
  CT -- "empty deny" --> PASS["Pass"]
  CT -- "deny found" --> FAIL["Fail the pipeline"]

Reusing it at runtime

The same Rego policies can run in OPA Gatekeeper as a Kubernetes admission controller, so rules in CI and in the cluster agree. Alternatives include Kyverno (YAML policies, next module), HashiCorp Sentinel in Terraform Cloud, Azure Policy and AWS SCPs.

Good practice

Traps: writing policies against the .tf code structure instead of the plan, so modules and variables slip past; policies without tests, where a small change makes them always pass.

Practice interactively →