Shayl.Taveras
portfolio / projects / rego-policy-library
← back to portfolio
// Project Walkthrough · 05
Rego Compliance Policy Library

Three OPA Rego policies that run as a pre-apply gate against terraform plan -json output, validating GCP resources against NIST 800-53 SC-28, AC-3, and CM-6 before anything is deployed. Each deny message returns the resource address and control ID so developers fix the problem without filing a GRC ticket.

opa rego nist 800-53 terraform · gcp · policy-as-code
Problem Statement
01

Most compliance validation happens after infrastructure is deployed. A resource gets created, someone runs a check, a finding gets filed, and the team spends a sprint remediating something that should never have been deployed in the first place. The loop is slow, reactive, and produces GRC tickets as a side effect of normal engineering work.

This library moves the validation upstream. Policies run against the Terraform plan before apply — no cloud access required, no resources created, instant feedback. A developer who tries to deploy an unencrypted bucket or a resource missing compliance labels sees the denial, the resource address, and the NIST control ID in the same terminal window where they ran the plan.

How It Works
02

OPA evaluates Rego policies against the JSON output of terraform plan -json. The plan contains every resource Terraform intends to create or modify, including all attribute values. Policies inspect those values and emit deny messages for any resource that fails a control check.

// pre-apply gate
Shift Left, Not Post-Deploy
Validation runs at plan time — before any API call reaches GCP. A failing policy blocks the apply entirely, not after resources are half-created.
// rego v1 semantics
OPA import rego.v1
Strict Rego v1 syntax with explicit import rego.v1 for forward compatibility. Partial set rules with contains to recurse into child_modules for module-wrapped resources.
// actionable denials
Resource Address + Control ID
Every deny message returns the full resource address and the NIST control ID. Developers know exactly what to fix and why without a GRC ticket or Slack thread.
// in-memory tests
No Cloud Access Needed
The test suite runs entirely against in-memory fixtures — no apply, no GCP credentials, instant CI feedback. Eight tests across three policies.
Policies and Controls
03

Three policies, each enforcing one NIST 800-53 control. Each runs independently so teams can adopt them incrementally.

SC-28 · encryption.rego
Encryption at Rest via CMEK
Validates that every google_storage_bucket resource has a customer-managed encryption key configured. A bucket using default Google-managed encryption fails the policy — SC-28 requires customer control of the key material. Deny message returns the bucket resource address and SC-28.
AC-3 · public_access.rego
Public Access Lockdown
Checks both google_storage_bucket resources (uniform_bucket_level_access must be true, public_access_prevention must be enforced) and google_compute_firewall rules (no ingress rules allowing 0.0.0.0/0 on sensitive ports). Any resource that exposes access beyond the account boundary fails. Deny returns the resource address and AC-3.
CM-6 · labels.rego
Required Compliance Labels
Iterates all taggable resource types and validates that required compliance labels are present. Uses set subtraction — required_labels minus provided_labels — to identify exactly which labels are missing. Deny message returns the resource address, the missing label keys, and CM-6. No false positives on resources that do not support labels.
Key Technical Decisions
04
DecisionReasoning
import rego.v1Strict Rego v1 semantics for forward compatibility — avoids deprecated syntax that will break in future OPA releases
Partial set rules + containsRecurses into child_modules in the plan JSON so policies catch resources wrapped inside Terraform modules, not just root-level resources
Set subtraction for labelsrequired_labels - provided_labels surfaces exactly which labels are missing per resource — cleaner than a loop and easier to extend
No METADATA blocksSimple control coverage comments instead of YAML METADATA blocks avoids OPA parser issues and keeps policies readable without tooling
In-memory test fixturesTests run against hardcoded plan JSON fixtures — no cloud credentials, no apply, instant feedback in CI pipelines
Usage
05
// run policies against a terraform plan
# Generate plan JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > plan.json

# Evaluate all policies
opa eval -d policies/ -i plan.json "data.compliance.deny"

# Run the test suite (no cloud access needed)
opa test policies/ tests/ -v

# Example deny output:
# "google_storage_bucket.data_lake does not have CMEK — SC-28"
# "google_storage_bucket.logs is missing labels: [env, owner] — CM-6"
File Structure
06
tools/rego-policies/
  policies/
    encryption.rego # SC-28 — CMEK enforcement
    public_access.rego # AC-3 — public access lockdown
    labels.rego # CM-6 — required compliance labels
  tests/
    encryption_test.rego
    public_access_test.rego
    labels_test.rego
  fixtures/ # in-memory plan JSON test fixtures
  README.md
Summary
07
3
policies
8
tests passing
3
controls enforced
0
cloud calls needed
Project Details
type
policy-as-code
engine
OPA + Rego v1
cloud
GCP
input
terraform plan -json
gate
pre-apply
tests
8 passing
cloud access
not required
Controls
SC-28
Encryption at Rest
AC-3
Access Enforcement
CM-6
Config Settings
Framework
NIST 800-53
Rev 5
SC-28
encryption.rego
AC-3
public_access.rego
CM-6
labels.rego
Links
github
portfolio