IaC Security: Securing Terraform & CloudFormation Before Deployment
Infrastructure as Code transformed how teams provision cloud resources — but it also codified misconfigurations. A single misconfigured Terraform resource block can expose an S3 bucket to the internet or grant admin access to every IAM user. IaC security scanning catches these issues before they become production vulnerabilities.
What Is IaC Security?
IaC security is the practice of scanning infrastructure definition files — Terraform HCL, CloudFormation YAML/JSON, Kubernetes manifests, Dockerfiles, and Helm charts — for security misconfigurations, compliance violations, and hardcoded secrets before those resources are provisioned.
Unlike CSPM, which scans running cloud resources, IaC security operates on the source code that defines infrastructure. This means misconfigurations are caught in the pull request, before terraform apply or aws cloudformation deploy executes. The fix is a code change, not a manual console reconfiguration.
Common IaC Misconfigurations
These are the misconfigurations that IaC scanners catch most frequently. Each one has been involved in real-world data breaches.
Public S3 Buckets
S3 buckets with public ACLs or missing Block Public Access settings. Responsible for numerous high-profile data exposures.
Open Security Groups
Security groups with ingress rules allowing 0.0.0.0/0 on sensitive ports (SSH, RDP, database ports).
Unencrypted Storage
EBS volumes, RDS instances, and S3 buckets without encryption at rest enabled. Violates most compliance frameworks.
Overprivileged IAM Policies
IAM policies using wildcards (Action: *, Resource: *) instead of least-privilege permissions.
Missing Logging
CloudTrail, VPC Flow Logs, or access logging disabled. Without logs, breaches go undetected.
Hardcoded Secrets
API keys, database passwords, or tokens embedded directly in Terraform variables or CloudFormation parameters.
Default VPC Usage
Deploying resources into the default VPC which has permissive networking rules and no custom security controls.
Missing Tags
Resources without required tags for cost allocation, ownership tracking, and compliance classification.
Terraform Security Best Practices
IaC Scanning Tools Comparison
| Tool | Terraform | CloudFormation | K8s / Helm | Dockerfile | Custom Rules |
|---|---|---|---|---|---|
| Checkov | Yes | Yes | Yes | Yes | Python / YAML |
| tfsec (Trivy) | Yes | Yes | Yes | Yes | Rego / JSON |
| Terrascan | Yes | Yes | Yes | Yes | Rego |
| cfn-lint | — | Yes | — | — | Python plugins |
| KICS | Yes | Yes | Yes | Yes | Rego |
| Snyk IaC | Yes | Yes | Yes | Yes | Policy engine |
| TigerGate | Yes | Yes | Yes | Yes | YAML rules |
Most teams start with Checkov or tfsec (now part of Trivy) for open-source IaC scanning, then layer in a platform like TigerGate for unified scanning across code, infrastructure, containers, and cloud posture with centralized policy management.
Integrating IaC Scanning into CI/CD
Where and how you run the scanner matters as much as which scanner you pick. A layered rollout looks like this.
Pre-commit: run the scanner locally via a pre-commit hook so engineers get feedback in seconds, before a PR even exists. Checkov, Trivy, and Terrascan all ship pre-commit integrations. This catches the obvious cases — a wide-open ingress rule, a missing encryption block — at the cheapest possible point.
Pull request: run the full scan in CI and post findings as inline PR comments on the exact resource block, not as a wall of text in a build log. Fail the check only for high-confidence, high severity rules. A typical GitHub Actions step is a single command: checkov -d . --check HIGH,CRITICAL --compact.
Plan time: scanning raw HCL misses what modules, variables, and count/for_each expressions resolve to at runtime. Scanning the JSON output of terraform plan -out=tfplan && terraform show -json tfplan evaluates the actual resources that would be created, including values passed into third-party modules. This is the most accurate scan point and the right place for a hard gate before apply.
Post-deploy: CSPM scanning of the live account closes the loop, catching both resources created outside IaC and drift introduced by console changes. When the same platform runs IaC and cloud scans — as TigerGate does — findings can be matched: a live misconfiguration traces back to the Terraform file that defines the resource, so the fix lands in code.
The Overlooked Attack Surface: State Files and Modules
Scanning resource definitions is necessary but not sufficient — Terraform's operational machinery has its own attack surface that scanners largely ignore.
State files contain secrets
Terraform state stores resource attributes in plaintext JSON — including database passwords, generated private keys, and API tokens, even when the corresponding variables are marked sensitive. A readable state bucket is functionally equivalent to a leaked secrets vault. Lock down the state backend with a dedicated IAM policy, enable bucket encryption and versioning, block public access explicitly, and never commit terraform.tfstate to git. Where possible, keep secrets out of state entirely by referencing them at runtime (e.g., an RDS password managed by Secrets Manager with manage_master_user_password).
Modules are a supply chain
A third-party module from the Terraform Registry executes with the full privileges of your pipeline's cloud credentials. Unpinned module sources (ref=main instead of a version or commit hash) mean your infrastructure changes when someone else pushes code. Pin module versions, review module source before first use, prefer a curated internal registry of vetted modules, and remember that malicious providers are also possible — provider checksums in .terraform.lock.hcl exist for exactly this reason and belong in version control.
The pipeline is the privilege
The CI job that runs terraform apply typically holds near-admin cloud credentials, which makes it a prime target. Use OIDC federation instead of static keys, split plan (runs on PRs, read-only role) from apply (runs on merge, write role, protected environment), and require human approval between them for production workspaces.
CloudFormation-Specific Security Practices
CloudFormation teams face the same misconfiguration classes but with different mechanics. Parameters holding secrets should always set NoEcho: true — and even then, prefer dynamic references such as {{resolve:secretsmanager:MySecret}} so the secret never appears in the template, the console, or the change set at all. Templates with hardcoded credentials are among the most common findings cfn-lint and Checkov report.
Three native features deserve wider use. First, change sets: always review a change set before executing a stack update, exactly as you would review a Terraform plan — pay particular attention to resources flagged for replacement, which can silently delete data. Second, stack policies and deletion protection: a stack policy can forbid updates to critical resources like production databases, and DeletionPolicy: Retain prevents a careless stack deletion from destroying stateful resources. Third, CloudFormation Guard (cfn-guard), AWS's own policy-as-code tool, which validates templates against rules written in a purpose-built DSL and integrates cleanly into CI alongside cfn-lint.
Finally, scope the service role. When CloudFormation assumes a role to provision resources, that role's permissions — not the caller's — define the blast radius. A least-privilege service role per stack prevents a compromised template from creating IAM users or modifying unrelated infrastructure, a control that has no direct Terraform equivalent and is frequently left at AdministratorAccess.
Beyond Built-in Rules: Policy as Code
Off-the-shelf rules encode generic best practice; policy as code encodes your rules — the ones auditors and architects actually care about. Examples: every S3 bucket must use a customer-managed KMS key from the security account; production databases may only live in the two approved regions; every resource must carry a cost-center tag matching a defined pattern; no IAM role may trust an account outside the organization.
Two ecosystems dominate. OPA/Rego (used by Conftest, Terrascan, and KICS) evaluates the Terraform plan JSON against declarative policies, and the same Rego skills transfer to Kubernetes admission control. Checkov takes a lower-friction path: custom rules in Python or YAML, which most platform teams find faster to author. Sentinel fills the same role for teams on HCP Terraform / Terraform Enterprise, with the advantage of running inside the plan/apply workflow natively.
Whichever engine you choose, treat policies like production code: version them in their own repository, write test cases with known-good and known-bad fixtures, and stage rollouts — new policies start as warnings, graduate to soft-fail with an exception process, and only then become hard gates. An exception process matters more than teams expect: a policy with no documented escape hatch gets bypassed with --skip-check comments scattered through the codebase, which is worse than having no policy at all because it looks like coverage.
Finally, map policies to compliance frameworks from day one. Most scanners annotate rules with CIS, PCI DSS, HIPAA, and SOC 2 references; preserving those mappings in your custom rules means every passing pipeline run doubles as audit evidence, and a failed control maps directly to the framework requirement it would violate.
Frequently Asked Questions
Does IaC scanning replace CSPM?
No — they are complementary. IaC scanning prevents misconfigurations from being deployed, but it cannot see resources created by hand, by other tools, or before IaC adoption. CSPM sees everything running but only after the fact. Mature teams run both and correlate the results, so a live finding routes to the Terraform file that manages the resource.
Our scanner reports hundreds of findings on legacy code. Where do we start?
Establish a baseline: suppress existing findings with a documented inventory, then enforce a zero-new-findings policy on changed files. Burn down the baseline by severity — public exposure and hardcoded secrets first. This keeps the pipeline green for developers while the backlog shrinks on a schedule rather than blocking all delivery on day one.
Should we scan Terraform HCL or the plan output?
Both. HCL scanning is fast and works pre-commit without cloud credentials, but it cannot fully resolve modules and variables. Plan-JSON scanning is slower and needs init/plan to run, but evaluates the exact resources that would be created. Use HCL scanning for early feedback and plan scanning as the authoritative gate before apply.
Scan Your Infrastructure as Code with TigerGate
TigerGate scans Terraform, CloudFormation, Kubernetes manifests, and Dockerfiles against 500+ security rules mapped to CIS Benchmarks, SOC 2, and PCI DSS.