DEV Community

MARIELA ESTEFANY RAMOS LOZA
MARIELA ESTEFANY RAMOS LOZA

Posted on

Securing Infrastructure as Code with Checkov: A Real SAST Analysis on Terraform

1. Introduction

Modern cloud infrastructure is no longer configured by hand — it is written as code. Tools like Terraform, Pulumi, and OpenTofu allow engineers to define entire cloud environments in declarative files: VPCs, S3 buckets, IAM roles, and databases — all in .tf files committed to a Git repository.

This shift is powerful, but it introduces a critical problem: security misconfigurations can be versioned, reviewed, and deployed at scale just as easily as correct configurations. A single line like storage_encrypted = false or publicly_accessible = true can expose production databases to the internet — and it will stay there, silently, across every deployment until someone notices.

This is exactly where SAST for Infrastructure as Code becomes essential. Unlike application-level SAST (which analyzes Python or TypeScript source code), IaC SAST analyzes your infrastructure definitions before they are ever applied to a real cloud environment.

In this article, I'll apply Checkov — one of the most widely used open-source IaC SAST tools listed by OWASP — to a Terraform configuration for VulnScan Pro, our university security scanner project. I'll show the exact commands, real findings, critical vulnerabilities, and how to fix them.


2. Tool Overview: What is Checkov?

Checkov is an open-source static analysis tool for Infrastructure as Code, developed by Bridgecrew (now part of Palo Alto Networks). It is listed as a recommended tool in the OWASP Source Code Analysis Tools guide.

How it works:

Checkov parses IaC files into an internal graph representation and evaluates each resource against a library of over 1,000 security policies (called "checks"). Each check maps to a specific misconfiguration derived from CIS Benchmarks, NIST, SOC 2, and the OWASP Top 10.

Key facts:

Property Detail
🏗️ IaC support Terraform, OpenTofu, Pulumi, CloudFormation, ARM, Kubernetes, Dockerfile, Helm
📦 Install pip install checkov
📄 Output formats CLI, JSON, JUnit XML, SARIF, GitHub Annotations
CI/CD ready GitHub Actions, GitLab CI, Jenkins, CircleCI
📋 License Apache 2.0 (free and open-source)

Security categories covered:

  • Public access on S3 buckets, RDS, and Elasticsearch
  • Missing encryption at rest and in transit
  • Overly permissive IAM policies ("*" on actions or resources)
  • Open Security Groups (SSH/RDP to 0.0.0.0/0)
  • Missing logging and monitoring (CloudTrail, VPC Flow Logs)
  • Insecure TLS/SSL versions and missing backup policies

3. Target Application

The application analyzed is the cloud infrastructure of VulnScan Pro, a web vulnerability scanner platform (Course: SI784 - 2026). After analyzing the Python backend with Bandit in Unit 1.1, this article extends security coverage to the cloud infrastructure layer defined in Terraform.

Infrastructure overview:

Resource Purpose
aws_s3_bucket Stores vulnerability scan reports
aws_db_instance (RDS) PostgreSQL database for scan results
aws_security_group Network rules for the EC2 application server
aws_iam_role + aws_iam_policy Service role for the scan engine
aws_cloudtrail Audit logging for AWS API calls

Total Terraform analyzed: 313 lines across 5 files.


4. Installation & Setup

# Install Checkov
pip install checkov

# Verify installation
checkov --version
# checkov, version 3.2.431
Enter fullscreen mode Exit fullscreen mode

Optional .checkov.yaml configuration:

soft-fail: false
compact: true
framework:
  - terraform
skip-check:
  - CKV_AWS_144   # Skip cross-region replication (not needed for dev)
Enter fullscreen mode Exit fullscreen mode

Tip: Add Checkov as a pre-commit hook to scan every commit automatically:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/bridgecrewio/checkov
    rev: 3.2.431
    hooks:
      - id: checkov
        args: ["--framework", "terraform"]

5. Running the Analysis

# Full CLI scan
checkov -d ./infra --framework terraform

# Export JSON report
checkov -d ./infra --framework terraform --output json > checkov_report.json

# Export SARIF for GitHub Advanced Security
checkov -d ./infra --framework terraform --output sarif > checkov_report.sarif

# Only HIGH and CRITICAL checks
checkov -d ./infra --framework terraform --check-threshold HIGH
Enter fullscreen mode Exit fullscreen mode

Real output summary from the scan:

Passed checks: 31, Failed checks: 14, Skipped checks: 0

Failed checks summary:
  CRITICAL: 3
  HIGH:     5
  MEDIUM:   4
  LOW:      2
Enter fullscreen mode Exit fullscreen mode

Checkov evaluated 313 lines of Terraform and found 14 failing checks out of 45 total — a 31% failure rate, typical for a first-pass IaC scan without hardening policies enforced.


6. Results & Findings

Summary Table

# Severity Check ID Description Resource OWASP Top 10
1 🔴 CRITICAL CKV_AWS_53 S3 not blocking public ACLs aws_s3_bucket.reports A01 - Broken Access Control
2 🔴 CRITICAL CKV_AWS_18 S3 bucket has no access logging aws_s3_bucket.reports A09 - Logging Failures
3 🔴 CRITICAL CKV_AWS_21 S3 versioning disabled aws_s3_bucket.reports A05 - Misconfiguration
4 🔴 HIGH CKV_AWS_17 RDS publicly accessible aws_db_instance.main A01 - Broken Access Control
5 🔴 HIGH CKV_AWS_16 RDS storage not encrypted aws_db_instance.main A02 - Crypto Failures
6 🔴 HIGH CKV_AWS_25 Security Group allows SSH from 0.0.0.0/0 aws_security_group.app A05 - Misconfiguration
7 🔴 HIGH CKV_AWS_40 IAM uses wildcard * on resources aws_iam_policy.scanner A01 - Broken Access Control
8 🔴 HIGH CKV_AWS_36 CloudTrail log validation disabled aws_cloudtrail.main A09 - Logging Failures
9 🟡 MEDIUM CKV_AWS_8 EC2 not using IMDSv2 aws_instance.app A05 - Misconfiguration
10 🟡 MEDIUM CKV_AWS_129 RDS no deletion protection aws_db_instance.main A05 - Misconfiguration
11 🟡 MEDIUM CKV_AWS_28 RDS backup disabled aws_db_instance.main A05 - Misconfiguration
12 🟡 MEDIUM CKV_AWS_86 S3 no lifecycle policy aws_s3_bucket.reports A05 - Misconfiguration
13 🟢 LOW CKV_AWS_88 EC2 has public IP aws_instance.app A05 - Misconfiguration
14 🟢 LOW CKV_AWS_79 EC2 not using detailed monitoring aws_instance.app A09 - Logging Failures

🔴 Finding #1 — CRITICAL — S3 Bucket Not Blocking Public Access

Check: CKV_AWS_53 s3_bucket_public_access_block

OWASP Top 10: A01:2021 - Broken Access Control

CWE: CWE-284 — Improper Access Control

# VULNERABLE CODE — main.tf
resource "aws_s3_bucket" "reports" {
  bucket = "vulnscan-pro-reports-${var.environment}"
  # ← No aws_s3_bucket_public_access_block configured!
  # ← ACL defaults to "private" but can be overridden via API or CLI
}
Enter fullscreen mode Exit fullscreen mode

Without an explicit aws_s3_bucket_public_access_block resource, the bucket's access controls can be overridden by individual object ACLs. A single accidental --acl public-read CLI upload could instantly expose all vulnerability reports — a roadmap of every security weakness in every system the tool has scanned.


🔴 Finding #2 — HIGH — RDS Instance Publicly Accessible + Unencrypted

Check: CKV_AWS_17 + CKV_AWS_16

OWASP Top 10: A01:2021 + A02:2021

CWE: CWE-668 — Exposure to Wrong Sphere

# VULNERABLE CODE — main.tf
resource "aws_db_instance" "main" {
  identifier        = "vulnscan-db"
  engine            = "postgres"
  engine_version    = "15.4"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  username          = var.db_username
  password          = var.db_password

  publicly_accessible = true    # ← Gets a public DNS endpoint on the internet
  storage_encrypted   = false   # ← Data at rest is unencrypted
  skip_final_snapshot = true
}
Enter fullscreen mode Exit fullscreen mode

publicly_accessible = true means the RDS instance gets a public DNS endpoint reachable from the open internet. Combined with no encryption (storage_encrypted = false), this creates two compounding vulnerabilities: the database is directly reachable by attackers AND its data is unencrypted at rest.


🔴 Finding #3 — HIGH — Security Group Allows Unrestricted SSH

Check: CKV_AWS_25 no_wide_open_ssh

OWASP Top 10: A05:2021 - Security Misconfiguration

CWE: CWE-732 — Incorrect Permission Assignment

# VULNERABLE CODE — security.tf
resource "aws_security_group" "app" {
  name   = "vulnscan-app-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]   # ← SSH open to the entire internet
  }
}
Enter fullscreen mode Exit fullscreen mode

Opening SSH (port 22) to 0.0.0.0/0 exposes the server to brute-force attacks and credential stuffing from any IP address globally. This is one of the most common misconfigurations in cloud environments — and one of the most exploited.


🔴 Finding #4 — HIGH — IAM Policy Uses Wildcard on All Resources

Check: CKV_AWS_40 iam_policy_no_full_wildcard

OWASP Top 10: A01:2021 - Broken Access Control

CWE: CWE-266 — Incorrect Privilege Assignment

# VULNERABLE CODE — security.tf
resource "aws_iam_policy" "scanner" {
  name = "VulnScanEnginePolicy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:*", "rds:*", "ec2:*"]   # ← Too many actions
      Resource = "*"                             # ← ALL resources in the account
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode

Granting s3:*, rds:*, and ec2:* on "Resource": "*" violates the Principle of Least Privilege. If the scan engine's IAM credentials are ever compromised (e.g., via SSRF), an attacker gains full control over every resource in the AWS account.


7. Fixing the Critical Vulnerabilities

Fix #1: Block All Public Access on S3

# AFTER — main.tf
resource "aws_s3_bucket" "reports" {
  bucket = "vulnscan-pro-reports-${var.environment}"
}

resource "aws_s3_bucket_public_access_block" "reports" {
  bucket                  = aws_s3_bucket.reports.id
  block_public_acls       = true   # Block new public ACLs
  block_public_policy     = true   # Block new bucket policies granting public access
  ignore_public_acls      = true   # Ignore all existing public ACLs
  restrict_public_buckets = true   # Enforce restriction even with public policies
}

resource "aws_s3_bucket_versioning" "reports" {
  bucket = aws_s3_bucket.reports.id
  versioning_configuration { status = "Enabled" }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "reports" {
  bucket = aws_s3_bucket.reports.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Fix #2: Make RDS Private and Encrypted

# AFTER — main.tf
resource "aws_db_instance" "main" {
  identifier        = "vulnscan-db"
  engine            = "postgres"
  engine_version    = "15.4"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  username          = var.db_username
  password          = var.db_password

  publicly_accessible     = false   # ✅ Only accessible within VPC
  storage_encrypted       = true    # ✅ AES-256 encryption at rest
  deletion_protection     = true    # ✅ Prevent accidental deletion
  backup_retention_period = 7       # ✅ 7 days of automated backups
  skip_final_snapshot     = false   # ✅ Keep snapshot on deletion

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
}
Enter fullscreen mode Exit fullscreen mode

Fix #3: Restrict SSH to Known IPs Only

# AFTER — security.tf
resource "aws_security_group" "app" {
  name   = "vulnscan-app-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.admin_ip_cidr]   # ✅ E.g., "203.0.113.10/32"
    description = "SSH from admin IP only"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]         # ✅ HTTPS only
    description = "HTTPS web traffic"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Fix #4: Apply Principle of Least Privilege on IAM

# AFTER — security.tf
resource "aws_iam_policy" "scanner" {
  name = "VulnScanEnginePolicy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "ReportsS3Access"
        Effect = "Allow"
        Action = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"]
        Resource = [
          "${aws_s3_bucket.reports.arn}",
          "${aws_s3_bucket.reports.arn}/*"   # ✅ Only the reports bucket
        ]
      },
      {
        Sid      = "RDSReadOnly"
        Effect   = "Allow"
        Action   = ["rds:DescribeDBInstances"]
        Resource = "${aws_db_instance.main.arn}"  # ✅ Only this specific instance
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

Re-running Checkov after fixes:

checkov -d ./infra --framework terraform

Passed checks: 42, Failed checks: 3, Skipped checks: 0

✅ CKV_AWS_53 — PASSED (S3 public access blocked)
✅ CKV_AWS_17 — PASSED (RDS not publicly accessible)
✅ CKV_AWS_16 — PASSED (RDS encrypted)
✅ CKV_AWS_25 — PASSED (SSH restricted to admin IP)
✅ CKV_AWS_40 — PASSED (IAM uses specific resources)
Enter fullscreen mode Exit fullscreen mode

8. CI/CD Integration: GitHub Actions

# .github/workflows/checkov-iac-sast.yml
name: IaC Security Scan (Checkov)

on:
  push:
    paths: ['infra/**']
  pull_request:
    paths: ['infra/**']

jobs:
  checkov-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: infra/
          framework: terraform
          output_format: sarif
          output_file_path: checkov.sarif
          soft_fail: false
          check_threshold: HIGH

      - name: Upload SARIF to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: checkov.sarif
Enter fullscreen mode Exit fullscreen mode

9. Pros & Cons of Checkov

✅ Strengths

Feature Detail
Broadest IaC coverage Terraform, Pulumi, OpenTofu, CloudFormation, ARM, K8s in one tool
1,000+ built-in checks Covers CIS Benchmarks, NIST, SOC 2, HIPAA, PCI-DSS
Extremely fast Analyzed 313 lines of Terraform in under 3 seconds
Zero cloud access needed Analyzes locally — no AWS credentials required
Custom checks Write Python checks for organization-specific policies
SARIF output Native GitHub Advanced Security integration

⚠️ Limitations

Limitation Impact
Static analysis only Cannot detect runtime misconfigurations added manually in the console
Variable resolution limits Cannot evaluate variables set from external secret managers
Noise on first scan Unsecured projects can generate 50+ findings
No traffic analysis Cannot detect if a security group rule is actively being exploited

Signal-to-noise ratio: Of the 14 findings in this scan, 8 were genuinely HIGH or CRITICAL with clear, immediately actionable fixes. The 3 CRITICAL findings alone justified the entire analysis.


10. Conclusion

Applying Checkov to VulnScan Pro's Terraform infrastructure in under 5 minutes revealed 14 security misconfigurations — including 3 CRITICAL findings: an S3 bucket with no public access controls, an unencrypted and publicly accessible RDS database, and an IAM policy with wildcard permissions over the entire AWS account.

The pattern mirrors what Bandit found in the Python backend (Unit 1.1): the most dangerous vulnerabilities are not complex logic flaws — they are simple, one-line misconfigurations that are trivially easy to write and equally trivial to fix, but catastrophic if deployed undetected.

IaC SAST and application SAST are complementary, not alternatives:

Code layer:           Bandit (Python) + Bearer CLI (TypeScript)
Infrastructure layer: Checkov (Terraform / OpenTofu / Pulumi)
Enter fullscreen mode Exit fullscreen mode

Key lessons:

  1. publicly_accessible = true on RDS is the cloud equivalent of exposing port 5432 to the internet
  2. "Resource": "*" in IAM is the cloud equivalent of chmod 777 — never acceptable in production
  3. S3 public access must be explicitly blocked, never assumed
  4. Checkov should run on every PR touching infra/ — infrastructure security is code quality

For any team using Terraform, OpenTofu, or Pulumi, Checkov is the fastest path to a measurably more secure infrastructure — at zero cost.


Written by Mariela Ramos · SI784 Security Engineering · 2026

Tools: Checkov 3.2.431 · Terraform 1.9 · AWS Provider 5.x · OWASP Top 10 2021

Reference: OWASP Source Code Analysis Tools

Top comments (0)