{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreigxqrtpky2ukilegcrw76aftomoblpuzhdrvo6lzmuchu4ilzzyca",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpubj55qwn72"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreifjbmbziw6t5rg4kpb42ufzuqhpjjmy4ed7qtlfxwtedbsupg7nt4"
    },
    "mimeType": "image/webp",
    "size": 77000
  },
  "path": "/dayan_elvisjahuirapilco/your-infrastructure-has-bugs-too-scanning-terraform-with-checkov-iac-sast-5fdk",
  "publishedAt": "2026-07-04T23:24:24.000Z",
  "site": "https://dev.to",
  "tags": [
    "security",
    "terraform",
    "devsecops",
    "aws",
    "Checkov",
    "Source Code Analysis Tools",
    "GitHub repo →",
    "previous article",
    "github.com/Dayan-18/checkov-demo",
    "OWASP Source Code Analysis Tools",
    "Checkov documentation",
    "Terraform AWS provider docs"
  ],
  "textContent": "> **TL;DR:** Application code isn't the only thing that ships vulnerabilities — your Terraform does too. I wrote an intentionally insecure AWS configuration, scanned it with Checkov (a SAST tool for Infrastructure as Code, listed on the OWASP Source Code Analysis Tools page), went from **35 failed checks to 0** , and wired the scan into GitHub Actions. Full code: **GitHub repo →**\n\n##  SAST for infrastructure? Yes, that's a thing\n\nIn my previous article I used Bandit to find security bugs in Python code. But modern applications are deployed with **Infrastructure as Code** (Terraform, Pulumi, OpenTofu, CloudFormation) — and a misconfigured S3 bucket has caused more real-world data breaches than most code vulnerabilities.\n\nThe good news: since infrastructure is now _code_ , it can be statically analyzed like code. That's exactly what **Checkov** does: an open-source tool (Python, maintained by Prisma Cloud) with 1,000+ built-in policies that scans Terraform, CloudFormation, Kubernetes manifests, Dockerfiles, and more — no cloud credentials needed, it never touches your AWS account.\n\n##  The target: a deliberately insecure AWS stack\n\nI wrote a `main.tf` that concentrates the misconfigurations behind many famous breaches:\n\n\n\n    # Issue 1: S3 bucket - public, unencrypted, no versioning, no logging\n    resource \"aws_s3_bucket\" \"data\" {\n      bucket = \"company-customer-data-bucket\"\n      acl    = \"public-read\"\n    }\n\n    # Issue 2: security group open to the entire internet, including SSH\n    resource \"aws_security_group\" \"web\" {\n      name = \"web-sg\"\n\n      ingress {\n        from_port   = 22\n        to_port     = 22\n        protocol    = \"tcp\"\n        cidr_blocks = [\"0.0.0.0/0\"]\n      }\n\n      ingress {\n        from_port   = 0\n        to_port     = 65535\n        protocol    = \"tcp\"\n        cidr_blocks = [\"0.0.0.0/0\"]\n      }\n    }\n\n    # Issue 3: database publicly accessible, unencrypted, hardcoded password\n    resource \"aws_db_instance\" \"db\" {\n      identifier          = \"app-db\"\n      engine              = \"mysql\"\n      instance_class      = \"db.t3.micro\"\n      allocated_storage   = 20\n      username            = \"admin\"\n      password            = \"SuperSecret123!\"\n      publicly_accessible = true\n      storage_encrypted   = false\n      skip_final_snapshot = true\n    }\n\n    # Issue 4: EC2 instance without IMDSv2 and with unencrypted root volume\n    resource \"aws_instance\" \"app\" {\n      ami           = \"ami-0c55b159cbfafe1f0\"\n      instance_type = \"t3.micro\"\n\n      vpc_security_group_ids = [aws_security_group.web.id]\n\n      root_block_device {\n        encrypted = false\n      }\n    }\n\n    # Issue 5: IAM policy with full admin wildcard\n    resource \"aws_iam_policy\" \"admin\" {\n      name = \"app-policy\"\n\n      policy = jsonencode({\n        Version = \"2012-10-17\"\n        Statement = [{\n          Effect   = \"Allow\"\n          Action   = \"*\"\n          Resource = \"*\"\n        }]\n      })\n    }\n\n\nFive resources. Looks harmless, right? Let's see.\n\n##  Running Checkov\n\n\n    pip install checkov\n    checkov -f main.tf\n\n\nThe verdict, in a few seconds and without touching AWS:\n\n\n\n    Passed checks: 14, Failed checks: 35, Skipped checks: 0\n\n\n**35 failed checks** in 70 lines of Terraform. Some highlights:\n\n###  🔴 The public S3 bucket (the classic breach)\n\n\n    Check: CKV_AWS_20: \"S3 Bucket has an ACL defined which allows public READ access.\"\n        FAILED for resource: aws_s3_bucket.data\n    Check: CKV2_AWS_6: \"Ensure that S3 bucket has a Public Access block\"\n        FAILED for resource: aws_s3_bucket.data\n    Check: CKV_AWS_145: \"Ensure that S3 buckets are encrypted with KMS by default\"\n        FAILED for resource: aws_s3_bucket.data\n\n\nA bucket named `company-customer-data-bucket` with `acl = \"public-read\"` — this exact pattern exposed hundreds of millions of records in real incidents (Capital One, Accenture, the US voter records leak...).\n\n###  🔴 The god-mode IAM policy\n\n\n    Check: CKV_AWS_62: \"Ensure IAM policies that allow full \"*-*\" administrative\n           privileges are not created\"\n        FAILED for resource: aws_iam_policy.admin\n    Check: CKV_AWS_286: \"Ensure IAM policies does not allow privilege escalation\"\n        FAILED for resource: aws_iam_policy.admin\n    Check: CKV_AWS_288: \"Ensure IAM policies does not allow data exfiltration\"\n        FAILED for resource: aws_iam_policy.admin\n\n\n`Action = \"*\"` on `Resource = \"*\"` earned **eight** failed checks by itself: privilege escalation, credentials exposure, data exfiltration... one wildcard, every attack path.\n\n###  🔴 SSH open to the world + public database\n\nCheckov also flagged the security group allowing `0.0.0.0/0` on port 22 (CKV_AWS_24), the RDS instance with `publicly_accessible = true` (CKV_AWS_17), no storage encryption (CKV_AWS_16), and the **hardcoded database password** sitting in version control.\n\n##  Fixing it\n\nThe remediated `main_fixed.tf` applies standard hardening:\n\nMisconfiguration | Fix\n---|---\nPublic S3 bucket |  `aws_s3_bucket_public_access_block` + private ACL\nUnencrypted bucket | KMS key with rotation + SSE configuration\nNo versioning/logging |  `aws_s3_bucket_versioning` + access logging to a second bucket\nSSH open to 0.0.0.0/0 | Ingress restricted to a trusted CIDR variable, HTTPS only\nHardcoded DB password |  `variable \"db_password\" { sensitive = true }` via `TF_VAR_db_password`\nPublic, unencrypted RDS |  `publicly_accessible = false`, `storage_encrypted = true`, multi-AZ\nEC2 metadata v1 |  `metadata_options { http_tokens = \"required\" }` (IMDSv2)\nAdmin wildcard IAM | Least privilege: `s3:GetObject` on one bucket ARN only\n\nTwo checks didn't apply to this workload (cross-region replication, S3 event notifications), so instead of silencing the scanner I documented the decision inline — Checkov's equivalent of Bandit's `# nosec`:\n\n\n\n    resource \"aws_s3_bucket\" \"data\" {\n      #checkov:skip=CKV_AWS_144:Single-region deployment, replication not required\n      #checkov:skip=CKV2_AWS_62:No downstream consumers need S3 event notifications\n      bucket = \"company-customer-data-bucket\"\n    }\n\n\nThe result:\n\n\n\n    Passed checks: 79, Failed checks: 0, Skipped checks: 5\n\n\n**From 35 failed checks to 0** , with every skip justified and auditable. ✅\n\n##  Automating with GitHub Actions\n\nSame principle as any SAST tool: a manual scan is a snapshot, a CI scan is a security gate. This workflow runs on every push and PR, and fails the build if the hardened configuration regresses:\n\n\n\n    name: Checkov IaC Scan\n\n    on:\n      push:\n        branches: [main]\n      pull_request:\n        branches: [main]\n\n    jobs:\n      checkov:\n        runs-on: ubuntu-latest\n        steps:\n          - uses: actions/checkout@v4\n          - uses: actions/setup-python@v5\n            with:\n              python-version: \"3.12\"\n          - name: Install Checkov\n            run: pip install checkov\n          - name: Scan vulnerable config (findings expected, does not block)\n            run: checkov -f main.tf --compact || true\n          - name: Scan fixed config (security gate)\n            run: checkov -f main_fixed.tf --compact\n          - name: Generate JSON report\n            if: always()\n            run: checkov -f main_fixed.tf -o json > checkov-report.json || true\n          - uses: actions/upload-artifact@v4\n            if: always()\n            with:\n              name: checkov-report\n              path: checkov-report.json\n\n\nNow a pull request that opens port 22 to the internet gets a red ❌ before any human even reviews it.\n\n##  Strengths and limitations\n\nWhat impressed me about Checkov: enormous policy coverage out of the box, scans finish in seconds with zero cloud credentials, checks map to real breach patterns, and the graph-based engine understands _connections_ between resources (that's what the `CKV2_*` checks do — for example, it knows my bucket lacks a Public Access Block _resource_ , not just a bad attribute).\n\nLimitations, in line with what OWASP says about SAST generally: it analyzes _declared_ state, not what actually runs in your account (drift is invisible to it); it can't know your business context, so expect policies that don't apply to your workload — that's what documented skips are for; and it won't catch logic flaws in how your services use the infrastructure. Combine it with cloud posture management (CSPM) and runtime monitoring for full coverage.\n\n##  Conclusion\n\nThe same lesson as application SAST, but the stakes are arguably higher: nobody exploits your off-by-one error as fast as a scanner finds your public S3 bucket. Checkov made 35 concrete problems visible in seconds, taught me the hardening pattern for each one, and now guards my infrastructure on every commit — for free.\n\n**Full demo code + workflow:** github.com/Dayan-18/checkov-demo\n\n_Do you scan your IaC in CI? Which tool? Tell me in the comments!_ 👇\n\n_References: OWASP Source Code Analysis Tools · Checkov documentation · Terraform AWS provider docs_",
  "title": "Your Infrastructure Has Bugs Too: Scanning Terraform with Checkov (IaC SAST)"
}