Infrastructure as Code (IaC) has revolutionized cloud deployments by making environments reproducible and version-controlled. But like any code, poorly written Terraform can ship critical vulnerabilities—misconfigured storage buckets, overprivileged IAM roles, or exposed services—that attackers exploit long before developers notice. The solution? Applying static application security testing (SAST) to your infrastructure files.
Security-focused tools like Checkov analyze Terraform, Kubernetes, Dockerfiles, and more without requiring cloud access. In this guide, we’ll walk through scanning an intentionally insecure AWS stack, remediating the issues, and automating checks in CI/CD pipelines to prevent regressions.
Infrastructure vulnerabilities are real—and often overlooked
Application code isn’t the only source of security flaws. A single misconfigured S3 bucket with public read access has led to breaches affecting millions of records in high-profile incidents. Yet many teams treat IaC as a "deployment checklist" rather than code that should undergo rigorous scrutiny.
Modern IaC tools like Terraform, Pulumi, and OpenTofu define entire cloud environments as declarative files. This means traditional SAST techniques—used for years to scan Python, JavaScript, or Java—can now be applied to infrastructure. Checkov, an open-source project maintained by Prisma Cloud, provides over 1,000 built-in policies to detect misconfigurations in Terraform, CloudFormation, Kubernetes manifests, and Dockerfiles. Crucially, it operates without cloud credentials, ensuring scans are fast, local, and safe.
Building an intentionally vulnerable Terraform stack
To demonstrate how Checkov identifies risks, we created a main.tf file with five resources deliberately mirroring common breaches:
- A publicly accessible S3 bucket storing sensitive customer data
- A security group allowing SSH (port 22) and all TCP ports from the internet
- A MySQL database exposed to the public, unencrypted, with a hardcoded password
- An EC2 instance without IMDSv2 (Instance Metadata Service version 2) and an unencrypted root volume
- An IAM policy granting full administrative privileges
Here’s a simplified version of the vulnerable configuration:
resource "aws_s3_bucket" "data" {
bucket = "company-customer-data-bucket"
acl = "public-read"
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "db" {
identifier = "app-db"
engine = "mysql"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "admin"
password = "SuperSecret123!"
publicly_accessible = true
storage_encrypted = false
skip_final_snapshot = true
}
resource "aws_instance" "app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
root_block_device {
encrypted = false
}
}
resource "aws_iam_policy" "admin" {
name = "app-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "*"
Resource = "*"
}]
})
}At first glance, this configuration might seem harmless. But in security terms, it’s a disaster waiting to happen.
Scanning with Checkov: from 35 failures to zero
Installing and running Checkov is straightforward. With Python already in place, the installation requires just one command:
pip install checkov
checkov -f main.tfWithin seconds, the scan reveals the extent of the vulnerabilities:
- Passed checks: 14
- Failed checks: 35
- Skipped checks: 0
The tool flags critical issues across multiple resources. The public S3 bucket alone triggers three separate failed checks, including CKV_AWS_20 for public read access and CKV_AWS_145 for lack of encryption. Real-world breaches like the Capital One incident prove how damaging such misconfigurations can be.
The IAM wildcard policy doesn’t fare any better. With Action = "*" and Resource = "*", Checkov identifies eight failed checks related to privilege escalation, credential exposure, and data exfiltration. A single line of code grants administrative access to every possible resource in the AWS account.
Other issues include:
- Security group allowing ingress from
0.0.0.0/0on port 22 (SSH) - Publicly accessible RDS instance without encryption
- Hardcoded database password in version control
- EC2 instance without IMDSv2 protection
- Unencrypted root volume on the instance
Each violation represents a potential attack vector. Yet without a dedicated IaC scanner, many teams would miss these risks until it’s too late.
Remediating vulnerabilities with least-privilege principles
To fix the configuration, we applied security best practices and re-scanned the file. The hardened main_fixed.tf introduces several key changes:
- Added
aws_s3_bucket_public_access_blockto restrict public access to the S3 bucket - Enabled server-side encryption with a KMS key and rotation
- Configured versioning and logging for the bucket
- Restricted SSH access to a trusted CIDR block and enforced HTTPS-only traffic
- Removed hardcoded database passwords using Terraform variables marked as sensitive
- Disabled public access for the RDS instance and enabled encryption
- Enforced IMDSv2 on the EC2 instance
- Replaced the wildcard IAM policy with a least-privilege role granting only necessary permissions
Some checks didn’t apply to this specific workload, such as cross-region replication or S3 event notifications. Instead of disabling these policies entirely, we documented the rationale inline using Checkov’s equivalent of # nosec:
resource "aws_s3_bucket" "data" {
#checkov:skip=CKV_AWS_144:Single-region deployment; replication not required
#checkov:skip=CKV2_AWS_62:No downstream consumers need S3 event notifications
bucket = "company-customer-data-bucket"
}After applying these fixes, the scan results improved dramatically:
- Passed checks: 79
- Failed checks: 0
- Skipped checks: 5
Every skipped check was justified and auditable, turning the scanner from a nuisance into a controlled gatekeeper.
Automating security gates in CI/CD
Static scans in a developer’s local environment are valuable, but they’re only as effective as the process enforces them. The real power of tools like Checkov lies in their integration with CI/CD pipelines.
A GitHub Actions workflow can run Checkov on every push or pull request, blocking merges if critical vulnerabilities are detected. This ensures security regressions are caught early in the development cycle. The workflow mirrors the manual scan but adds a fail-fast gate:
name: Terraform Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
quiet: trueBy treating IaC vulnerabilities with the same urgency as application code flaws, teams can prevent breaches before they occur. The combination of static analysis, remediation, and automation creates a repeatable security process for cloud infrastructure.
The future of secure infrastructure development
As cloud environments grow more complex, the line between application and infrastructure code continues to blur. Tools like Checkov are just the beginning. Expect to see tighter integration with CI/CD platforms, expanded policy libraries, and improved remediation guidance in the coming years.
The key takeaway? Infrastructure isn’t an afterthought—it’s part of the application. Scanning it with SAST tools isn’t optional; it’s a critical layer in a defense-in-depth security strategy. With the right processes in place, teams can deploy with confidence, knowing their cloud environments are as secure as their code.
AI summary
Learn how Checkov’s static analysis detects Terraform misconfigurations like public S3 buckets and overprivileged IAM policies before deployment. Includes a step-by-step remediation guide and CI/CD automation.