IaC compliance review (Terraform, verified)
Review a Terraform plan for security/compliance gaps and EU data residency, and prove it — findings come from a script that parses the real plan JSON and maps each to a control, not from eyeballing HCL.
Core principle
Compliance is checked, not claimed. The loop is: review the plan → triage findings by severity → fix the HCL → re-plan → re-review, until the gate is green at your blocking severities.
Be honest about scope (this is the rule that keeps the skill correct): this
is static plan review. It sees declared configuration, not runtime state,
drift, data flows, or anything outside the encoded policies and the providers
covered. Control "mapping" indicates relevance, not certified conformance.
It is not a substitute for a CSPM tool, a penetration test, or a formal
ISO 27001 / SOC 2 audit. Never report "ISO 27001 / SOC 2 / GDPR compliant" from
a green run — report "0 blocking findings against the encoded policy catalog."
→ references/01-scope-and-control-mapping.md
When to use vs. not
- Use for: a security/compliance review of Terraform; auditing cloud config for public exposure, encryption, IAM least-privilege, logging; checking EU data residency; mapping findings to ISO 27001 / SOC 2 / GDPR; gating Terraform in CI.
- Not for: runtime/posture scanning of a live account (use a CSPM/CNAPP), penetration testing, certifying an audit, or non-Terraform IaC the policy catalog doesn't cover (CloudFormation/Pulumi/ARM — out of scope here).
Inputs to gather first
- The plan as JSON — run
terraform plan -out tfplanthenterraform show -json tfplan > plan.json. The review reads that JSON; it never needs cloud credentials. →references/04-running-it.md - Allowed regions — the EU (or other) data-residency allow-list, e.g.
eu-central-1,eu-west-1,europe-west3. Empty = residency check off. - Gate severities — which severities fail the build; default
critical+high. Tighten to includemediumfor strict sign-off. - Required tags & waivers — governance tags every resource must carry, and
any
ignoreof a resource address or policy id (with a written reason).
Workflow
Load each reference when you reach its step.
Set scope & control mapping. Confirm this is static plan review and that a mapping ≠ certified conformance. →
references/01-scope-and-control-mapping.mdProduce the plan JSON and copy this skill's
scripts/into the project. No packages to install — the reviewer is Python 3.12 stdlib only. →references/04-running-it.mdterraform plan -out tfplan terraform show -json tfplan > plan.json cp scripts/iac-review.config.example.json scripts/iac-review.config.json # edit regions, gate, tagsRun the review and triage by severity. It writes
report.md+report.jsonand exits non-zero on blocking findings. →references/02-policy-catalog.mdpython3 scripts/iac_review.py \ --plan plan.json \ --config scripts/iac-review.config.json \ --out-dir iac-reportCheck data residency explicitly — every storage/db/volume region must be in the allowed EU set; flag international transfers. →
references/03-data-residency-gdpr.mdFix the HCL (root cause), re-
plan, re-show -json, re-review. Don't waive a finding to go green; useignoreonly with a written justification. →references/05-remediation.mdGate in CI on the script's exit code; archive
report.jsonas the evidence artifact. →references/04-running-it.md
What's in this skill
scripts/iac_review.py— the gate: walksplanned_values.root_module(recursingchild_modules) +resource_changes, applies the catalog, maps to ISO/SOC 2/GDPR, writesreport.{md,json}, prints PASS/FAIL, exits non-zero on blocking severities.scripts/policies.json— the policy catalog: each{id, title, appliesTo, severity, control{iso27001,soc2,gdpr}, rationale, remediation}.scripts/iac-review.config.example.json—allowedRegions,gateSeverities,requiredTags,ignore{addresses,policyIds}.scripts/plan.example.json— a tiny sample plan (a public+unencrypted bucket inus-east-1and a compliant encrypted bucket ineu-central-1) to self-test against.scripts/requirements.txt— stdlib-only; nothing to install (states so).references/01–05— scope & control mapping, the policy catalog, data residency/GDPR, running it (plan → show → review → CI), and remediation HCL snippets.
Definition of done
-
iac_review.pyruns over the currentplan.jsonand reports 0 findings at the configured gate severities (defaultcritical+high). - Every storage/db/volume region is in
allowedRegions(data residency), or the transfer is documented as an explicit, justified exception. - No public object storage, no
0.0.0.0/0on sensitive ports, no wildcardAction/ResourceIAM, encryption at rest on, audit logging present. -
requiredTagspresent on every taggable resource. - Any
ignoreentry has a written justification recorded in review. - CI runs the script and gates on its exit code;
report.jsonarchived. - Sign-off states "0 blocking findings against the encoded policy catalog" — not "ISO 27001 / SOC 2 / GDPR compliant."
Guardrails — avoid these mistakes
- Don't claim certified compliance from a green run. Control mappings show relevance; conformance needs a CSPM, a human auditor, and process evidence. Overclaiming is the cardinal error here.
- Review the plan, not the apply.
terraform show -jsonon a saved plan shows what will exist; a refresh-only or stale state misleads. - Don't waive to go green.
ignoreis for verified false positives or accepted risk with a written reason — never to silence a real finding. - Region absent ≠ compliant. If a resource has no region in the plan (provider default), residency can't be judged — set the region explicitly.
- Fix the cause in HCL, then re-plan and re-review; don't hand-edit the JSON or the report.
- The catalog is the scope. A clean run only means "nothing the encoded
policies and covered providers caught" — extend
policies.jsonas your controls grow.