Policy as Code (OPA)
The costAlertThresholdUsd gate in the config is a total-vs-budget comparison. For more expressive rules — per-tag, per-resource-kind, per-count — cloudrift supports Open Policy Agent (OPA).
How it works
cloudrift never runs OPA itself — it only produces JSON. You (or your CI pipeline) run conftest/opa against the JSON output:
node apps/cli/dist/main.js analyze --format json > report.json
conftest test --policy policy report.json
conftest exits 1 if any rule denies something, 0 if the report is clean — exactly the signal a CI step needs.
Prerequisites
Install conftest:
# macOS
brew install conftest
# Other platforms: https://www.conftest.dev/install/
Try it in 30 seconds (no AWS account needed)
The repo ships a sample report at policy/testdata/sample-report.json. You can see the policies fire immediately:
conftest test --policy policy policy/testdata/sample-report.json
Expected output — all three example policies deny something:
FAIL - policy/testdata/sample-report.json - main - 3 unattached EBS volumes found, more than the 2 allowed
FAIL - policy/testdata/sample-report.json - main - ebs-volume (vol-0abc123def456) in production is wasting $40/month
FAIL - policy/testdata/sample-report.json - main - total monthly waste $63.6 exceeds budget $50
Included example policies
| File | Rule |
|---|---|
policy/waste-budget.rego |
Total monthly waste over a fixed budget |
policy/production-tag.rego |
Any waste finding tagged Environment: production |
policy/idle-resource-count.rego |
More than N unattached EBS volumes |
Each has an associated _test.rego sibling. Run the policy tests:
opa test policy/ -v
Writing a custom rule
All policies share package main. Rego merges same-named deny contains msg if {...} blocks, so a fourth file just needs the same package header and its own condition. Example — deny any finding over $100/month:
# policy/high-cost-finding.rego
package main
import rego.v1
deny contains msg if {
some finding in input.findings
finding.category == "waste"
finding.monthlyCostUsd > 100
msg := sprintf("%s (%s) is wasting $%v/month", [finding.kind, finding.id, finding.monthlyCostUsd])
}
input is the parsed JSON report — available fields per finding: kind, category, tags, monthlyCostUsd, region, id, wasteReason. Top-level: totalWasteMonthlyUsd, wasteCount, findings[].
Rego gotcha: cloudrift serializes whole-dollar amounts without a decimal point (e.g.
$40becomes40, not40.00). Rego’s%.2fformat verb crashes on integers. Use%vin your rules.
Wire it into CI
Add a step after the scan:
- run: node cloudrift-cli/apps/cli/dist/main.js analyze -r us-east-1 --format json > report.json
working-directory: cloudrift-cli
- uses: openpolicyagent/conftest-action@v1
with:
policy: cloudrift-cli/policy
files: cloudrift-cli/report.json
Or install conftest directly and run conftest test --policy policy report.json.
Why external to the CLI
Embedding an OPA runtime (a shelled-out binary or WASM build) inside the npm package would add a heavy, platform-specific dependency to get a result that — for most users — a numeric comparison already provides. The value of a real policy engine only shows up with expressive, multi-signal rules, or when reusing an OPA/Rego bundle already maintained for Terraform or Kubernetes. Keeping OPA outside the package means cloudrift stays a lightweight CLI and anyone who wants this layer opts into it explicitly.