terraform-compliance Integration with DefectDojo
terraform-compliance Integration with DefectDojo
terraform-compliance is an open source, security focused BDD test framework for Terraform, published by the terraform-compliance project on GitHub. Policies are written as Gherkin scenarios in plain English ("Postgres servers must not use a public network") and evaluated against the JSON form of a Terraform plan, so violations are caught before anything is applied. It runs on the radish BDD runner and produces machine-readable output through radish's JUnit XML and Cucumber JSON writers, both of which DefectDojo imports.
terraform-compliance Integration with DefectDojo
We write our infrastructure guardrails as terraform-compliance scenarios because the policy reads like the requirement it enforces, and DefectDojo is where failed scenarios turn into tracked work. Each failed scenario imports as one Finding on the Asset that owns the Terraform code, named after the policy and, with JUnit output, the resource that broke it. Reimporting each plan's results shows which violations were fixed in the latest change and which are still open, and DefectDojo's SLA and assignment features apply to policy failures the same way they apply to code vulnerabilities.
Why terraform-compliance Matters
Most IaC scanners ship a fixed rule catalog. terraform-compliance takes the opposite approach: the team writes the rules, in language that security, platform, and audit staff can all read.
- Policies are scenarios such as "S3 buckets must have encryption enabled", which makes review and approval of the policy itself straightforward.
- It tests the plan, not just the source files, so values from variables, modules, and computed defaults are evaluated as Terraform resolved them.
- Failures are caught before
terraform apply, which is the cheapest point to fix a misconfiguration. - Because scenarios are code, they live in version control and change through the same review process as the infrastructure they govern.
Advantages of This Integration
- One Finding per policy requirement. A scenario is the unit a team writes and remediates, so a scenario that fails on its third step still imports as a single Finding rather than one per step.
- Resource-level detail with JUnit. With
--junit-xml, the parser pulls terraform-compliance's failure sentence apart to name the offending resource and property, and puts the resource in the title and Component Name. - Deduplication by policy and resource. Findings are hashed on the scenario name (Vulnerability ID from tool) and Component Name, so the same policy failing on the same resource across many plans stays one Finding.
- Reimport lifecycle. Reimporting into the same Test mitigates scenarios that now pass, adds new failures, and reactivates any that regressed.
- Grading you control. Every failure imports as Medium because terraform-compliance has no severity concept. In DefectDojo Pro, the Rules Engine can set severity on Findings matching a filter, such as a specific scenario name, so your most important policies carry the weight you assign them.
- Audit trail. Policy failures carry discovery dates, owners, notes, and risk acceptance records, which is the evidence auditors ask for.
How This Integration Works
DefectDojo imports results with the terraform-compliance Scan scan type. The parser detects the format from the file: content starting with < is read as JUnit XML, anything else as Cucumber JSON.
1. Prepare the plan. terraform-compliance evaluates a plan in JSON form. Create a plan with terraform plan -out=plan.out, then convert it with terraform show -json plan.out > plan.json.
2. Run the policies and write a report. terraform-compliance has no report flag of its own; it passes radish's writer flags through. JUnit XML is the preferred format:
terraform-compliance --features ./compliance --planfile plan.json --junit-xml report.xml
--cucumber-json report.json also imports, but radish records only whitespace as the failure message for terraform-compliance steps. Those Findings name the failed policy and the feature file line, but not the resource, and the description says so.
3. Import it. In the UI, open the Engagement, choose Import Scan Results, select terraform-compliance Scan, and upload the file. For automation, use the API in Community Edition or DefectDojo Pro:
curl "https://YOUR_INSTANCE/api/v2/import-scan/"
-H "Authorization: Token $DD_API_TOKEN"
-F "scan_type=terraform-compliance Scan"
-F "file=@report.xml"
-F "product_name=network-infra"
-F "engagement_name=Terraform Plans"
-F "auto_create_context=true"
DefectDojo Pro users can use Universal Importer:
universal-importer import
--defectdojo-url "https://YOUR_INSTANCE.cloud.defectdojo.com/"
--scan-type "terraform-compliance Scan"
--report-path "./report.xml"
--product-name "network-infra"
--engagement-name "Terraform Plans"
--auto-create-context
4. Reimport on each plan. For a workspace you plan repeatedly, send later reports to /api/v2/reimport-scan/ against the same Test.
Data Granularity: What Gets Imported
| DefectDojo Field | Source in terraform-compliance Report | Notes |
|---|---|---|
| Title | Scenario name, plus resource when known | JUnit: scenario: resource; Cucumber: scenario name |
| Severity | Fixed | Always Medium |
| Description | Failure message and scenario context | Failure sentence, scenario, feature, failed step, resource, property, and steps |
| Component Name | Failing resource (JUnit) or feature name | For example azurerm_postgresql_server.example |
| Vulnerability ID from tool | Scenario name | Stable handle for filtering and rules |
| File Path | Feature file uri |
Cucumber JSON only |
| Line | Line of the first failed step | Cucumber JSON only |
| Passing scenarios | Not imported | Only failed or errored scenarios become Findings |
| Finding type | Static | All findings are static |
| Deduplication | Hashcode | Vulnerability ID from tool, Component Name |
Use Cases
In a pull request pipeline: Every Terraform change runs terraform plan, then terraform-compliance, then a reimport into the workspace's Test. Reviewers see which policies the change breaks, and a gate can block merges while specific scenarios have active Findings.
Proving a control works: When an auditor asks how you prevent public database endpoints, the scenario text is the control and DefectDojo holds every past failure, who fixed it, and when.
Rolling out a new policy: A platform team adds a scenario requiring encryption on all storage. The first import across 25 workspaces shows how many resources fail it, and later reimports track the cleanup to zero.
Alongside rule-based IaC scanners: Teams that also run Checkov, KICS, or TFLint import those results into the same Asset. Catalog checks and organization-specific policies then show up in one place.
Operational Tips
- Prefer
--junit-xml. Cucumber JSON imports work, but without the resource name, two different resources failing the same scenario share one Component Name (the feature) and deduplicate together. - Name scenarios carefully and keep them stable. The scenario name is the Vulnerability ID from tool and part of the hashcode, so renaming a scenario makes its failures look new.
- In DefectDojo Pro, build Rules Engine rules keyed on scenario name to raise the severity of the policies that matter most to you.
- Use one Test per Terraform workspace or root module and reimport into it, so each environment has its own open-to-mitigated history.
- Tag imports with the workspace or environment name (for example
prodorstaging) so production violations can be filtered and given tighter SLAs. - If a scenario fails because of a deliberate exception, record a risk acceptance with an expiry date rather than weakening the scenario for everyone.