policy-as-code-runner
Configures policy-as-code testing using OPA / Conftest / Cedar - authors policies in Rego (OPA's language), runs Conftest against Kubernetes manifests / Terraform plans / Dockerfiles / arbitrary structured data, integrates with CI for PR-time policy gates. Per OPA's docs: "an open source, general-purpose policy engine that unifies policy enforcement across the stack." Use to express + enforce custom policies (cost limits, tagging requirements, security baselines) that Checkov / tfsec / KICS don't cover.
Install with skills.sh (any agent)
npx skills add testland/qa --skill policy-as-code-runnerpolicy-as-code-runner
Overview
Conftest is a CLI that uses OPA (opa-docs (opens in new window)) to test structured configuration files (Kubernetes manifests, Terraform plans, Dockerfiles, etc.) against Rego policies. This skill covers Conftest invocation + Rego policy authoring as the custom policy layer alongside Checkov / tfsec / KICS (which carry built-in checks).
When to use
Step 1 - Install Conftest
# macOS
brew install conftest
# Or download binary
curl -LO https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_<version>_Linux_x86_64.tar.gzStep 2 - Author a policy in Rego
# policies/kubernetes/required_labels.rego
package main
# All pods must have these labels
required_labels := {"app", "version", "owner"}
deny[msg] {
input.kind == "Deployment"
label := required_labels[_]
not input.spec.template.metadata.labels[label]
msg := sprintf("Deployment '%s' missing required label '%s'", [input.metadata.name, label])
}
# All deployments must specify resource limits
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container '%s' in deployment '%s' missing memory limit", [container.name, input.metadata.name])
}
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%s' in deployment '%s' missing CPU limit", [container.name, input.metadata.name])
}The deny rule pattern: each deny[msg] rule that holds adds a message to the deny set. If deny is non-empty, the test fails.
Step 3 - Run Conftest
# Test a single manifest
conftest test deployment.yaml
# Test all manifests
conftest test manifests/
# With specific policy directory
conftest test deployment.yaml --policy ./policies/
# JSON output for CI parsing
conftest test deployment.yaml --output jsonStep 4 - Test against Helm chart output
helm template myrelease charts/mychart/ -f values.yaml | conftest test -The chart renders to manifests; Conftest validates them.
Step 5 - Test against Terraform plan
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json --policy policies/terraform/# policies/terraform/cost_center_tag.rego
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
not resource.change.after.tags.cost_center
msg := sprintf("EC2 instance '%s' missing cost_center tag", [resource.address])
}Step 6 - Test against Dockerfile
# policies/dockerfile/no_latest.rego
package main
deny[msg] {
input[i].Cmd == "from"
val := input[i].Value
contains(val[0], ":latest")
msg := sprintf("Avoid :latest tag in FROM (line %d)", [i])
}
deny[msg] {
input[i].Cmd == "run"
val := input[i].Value[0]
contains(val, "apt-get upgrade")
msg := sprintf("Avoid 'apt-get upgrade' (line %d) - pin specific package versions", [i])
}conftest test DockerfileStep 7 - Author tests for policies
Policies themselves should be tested:
# policies/kubernetes/required_labels_test.rego
package main
test_deployment_missing_labels {
deny[_] with input as {
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": {"template": {"metadata": {"labels": {}}}}
}
}
test_deployment_with_all_labels {
count(deny) == 0 with input as {
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": {"template": {"metadata": {"labels": {"app": "x", "version": "1.0", "owner": "team"}}}, "spec": {"containers": [{"resources": {"limits": {"memory": "512Mi", "cpu": "500m"}}}]}}
}
}opa test policies/Step 8 - CI integration
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: |
curl -L https://github.com/open-policy-agent/conftest/releases/download/v0.50.0/conftest_0.50.0_Linux_x86_64.tar.gz | tar xz
sudo mv conftest /usr/local/bin/
- name: Test policies
run: opa test policies/
- name: Conftest manifests
run: conftest test manifests/ --policy policies/
- name: Conftest Helm
run: helm template myrelease charts/mychart/ -f values.yaml | conftest test - --policy policies/
- name: Conftest Terraform
run: |
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json --policy policies/terraform/Step 9 - Bundle policies for OPA Gatekeeper (Kubernetes admission)
For runtime enforcement (vs PR-time):
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: requiredlabels
spec:
crd:
spec:
names: { kind: RequiredLabels }
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
# Same Rego from Step 2Cluster admins install Gatekeeper; the constraint blocks Deployments that violate the policy at admission time.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Untested policies | Policy bugs let bad config through; over-strict policies block good config. | opa test against test fixtures (Step 7). |
| Policies in production without PR-time gating | Surprises at admission time; deployments fail. | CI gate first (Step 8); Gatekeeper second. |
| One mega-policy file | Hard to reason about; merge conflicts. | Per-domain policy files. |
| Rego complexity for simple checks | Hard to maintain; Rego learning curve. | Use Checkov / tfsec for built-in checks; OPA for custom. |
| Skipping the negation tests | Policy may pass for the wrong reason. | Test both deny and not-deny cases (Step 7). |
Limitations
References
Related skills
checkov-policy
Configures Checkov for IaC security scanning across Terraform, CloudFormation, Kubernetes, Helm, ARM, Serverless, AWS CDK - `pip install checkov`, custom Python checks, SARIF / JUnit output, and `--baseline` gating so CI fails only on new findings in legacy code. Use for the broadest built-in rule set with Python custom checks; for Terraform-only scanning use tfsec-policy, for wider platform breadth (OpenAPI / Pulumi / Crossplane) use kics-policy, and for a consolidated new-project scanner use trivy-config.
helm-chart-tester
Configures helm-unittest for Helm chart unit testing - installs the `helm-unittest` plugin, authors `tests/*.yaml` per template, asserts on rendered manifests (`isKind`, `equal`, `matchRegex`, snapshots), plus `helm lint` and `helm template` render testing. Use to verify a chart's template logic and rendered shape; this is unit-level correctness testing, not security scanning - to enforce policy on rendered manifests use policy-as-code-runner, and to scan charts for misconfigurations use checkov-policy or kics-policy.
kics-policy
Configures KICS (Keeping Infrastructure as Code Secure), Checkmarx's scanner covering Terraform, Kubernetes, Helm, Dockerfile, OpenAPI, Ansible, ARM, CloudFormation, Pulumi, Crossplane - CLI / Docker / GitHub Action / pre-commit, JSON / SARIF / HTML / JUnit output, custom Rego queries. Use for the widest platform breadth, especially OpenAPI / Pulumi / Crossplane; for the broadest built-in checks with Python custom rules use checkov-policy, for Terraform-only scanning use tfsec-policy, and for a consolidated scanner use trivy-config.
tfsec-policy
Configures tfsec for Terraform-specific security scanning - covers AWS / Azure / GCP / Kubernetes / OpenStack / Oracle / DigitalOcean / CloudStack, custom YAML rules, and SARIF / JUnit / Markdown output. Note: tfsec is transitioning to Trivy per Aqua Security, so new projects evaluate that first. Use for an existing Terraform-only tfsec stack; for the consolidated forward-path scanner use trivy-config, for the broadest multi-framework checks use checkov-policy, and for wider platform breadth use kics-policy.
trivy-config
Runs Trivy's misconfiguration scanner (`trivy config`) against IaC directories to detect security issues across Terraform, CloudFormation, Kubernetes manifests, Helm charts, Dockerfiles, and Azure ARM templates - installs Trivy, scans with severity gating via `--exit-code`, suppresses findings via `.trivyignore` / `.trivyignore.yaml` or inline annotations, extends built-in checks with custom Rego policies, and emits SARIF for GitHub Code Scanning. Trivy is the forward path from tfsec (per Aqua Security's own migration guidance). Use when adopting a consolidated IaC scanner for new projects, migrating away from tfsec, or scanning mixed IaC stacks with a single tool.