DEV Community

Cover image for πŸ›‘οΈ Secure, Lint, and Validate Your Terraform Like a Pro
4 1 1 2

πŸ›‘οΈ Secure, Lint, and Validate Your Terraform Like a Pro

β€œIt works, ship it!” β€” a phrase we’ve all heard. But when it comes to Infrastructure as Code (IaC), that mindset can lead to serious security and compliance issues.

πŸš€ Introduction: Terraform Isn’t Just Code β€” It’s Infrastructure

In today’s cloud-native environments, Infrastructure as Code (IaC) has become the backbone of scalable, consistent deployments. But with great power comes great responsibility.

A single misconfigured security group or a public S3 bucket can:

  • Expose sensitive data

  • Introduce downtime

  • Violate compliance standards

  • Lead to audit failures or breaches

πŸš€ Terraform = Real Code + Real Risk

As DevOps, SRE, and Platform Engineers, we know Terraform is code. But it’s not just code β€” it builds cloud infrastructure.

πŸ“‰ One security group open to the world?
πŸ“‰ One IAM policy with *:*?
πŸ“‰ One public S3 bucket?

⚠️ Real-World Pitfalls You Want to Avoid

🚫 Scenario 1: β€œOops, S3 Bucket Left Public”

resource "aws_s3_bucket" "data" {
  bucket = "my-sensitive-logs"
  acl    = "public-read"
}
Enter fullscreen mode Exit fullscreen mode
  • Risk: Unencrypted, public bucket

  • Tool that catches it: tfsec, checkov

🚫 Scenario 2: Overly Permissive Security Group

resource "aws_security_group" "ssh_access" {
  ingress {
    from_port   = 22
    to_port     = 22
    cidr_blocks = ["0.0.0.0/0"]
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Risk: Open SSH to the entire internet

  • Tool that flags it: tfsec, checkov

🚫 Scenario 3: IAM Wildcard Permissions

resource "aws_iam_policy" "admin_policy" {
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Action   = "*",
      Effect   = "Allow",
      Resource = "*"
    }]
  })
}

Enter fullscreen mode Exit fullscreen mode
  • Risk: Full access, violates least privilege

  • Tool that blocks it: checkov with CIS/NIST rules

➑️ That’s how breaches happen.
➑️ That’s why I never deploy Terraform code without this golden trio:

βœ… I never merge a PR without these:

πŸ”§ tflint – Lint for quality
πŸ” tfsec – Security misconfig scan
πŸ›‘οΈ checkov – Compliance & governance

Let’s dive deep into what they are, how they help, and how to automate them in CI/CD πŸ‘‡

βœ… Tool 1: tflint β€” The Terraform Linter

πŸ” What is tflint?

tflint is a static analysis linter for Terraform that catches bugs, anti-patterns, and style violations before you run terraform plan or apply.

🧠 Why it Matters

  • Finds unused variables, typos, and invalid resource attributes

  • Helps you write clean, readable, and consistent Terraform code

  • Enforces provider-specific best practices (AWS, Azure, GCP)

πŸ›  Common Issues Caught by tflint:

  • Using deprecated or invalid arguments

  • Misspelled variable or resource names

  • Misconfigured provider blocks

  • Poorly structured modules

tflint --init   # Downloads plugins
tflint          # Runs all checks

Enter fullscreen mode Exit fullscreen mode

🧠 Real-World Example:

Imagine a module with a typo like vpc_securty_group_ids. Terraform will silently skip it unless caught. tflint prevents these bugs early.

πŸ”— More Info: TFLint Documentation

πŸ” Tool 2: tfsec β€” Secure Terraform from the Start

πŸ” What is tfsec?

tfsec performs static analysis on your Terraform code to detect security vulnerabilities and misconfigurations.

It analyzes HCL files for patterns that can lead to:

  • Data leaks

  • Unintended access

  • Infrastructure exposure

πŸ”’ Example Findings:

Issue Severity Example
S3 Bucket ACL set to public High "acl" = "public-read"
SG open to the world on any port Critical "cidr_blocks = 0.0.0.0/0"
EBS volume not encrypted High encrypted = false
Secrets in user_data Medium Inline secrets in EC2 config

πŸ›  Command to Run:

tfsec .
Enter fullscreen mode Exit fullscreen mode

You create a quick EC2 setup:

resource "aws_security_group" "example" {
  ingress {
    from_port   = 22
    to_port     = 22
    cidr_blocks = ["0.0.0.0/0"]  # Dangerous
  }
}
Enter fullscreen mode Exit fullscreen mode

tfsec will immediately flag this as a high-risk issue.

πŸ›‘οΈ Tool 3: checkov β€” Policy-as-Code & Compliance Gate

πŸ” What is checkov?

checkov validates Terraform against security benchmarks and industry standards.

It enforces rules from:

  • πŸ” CIS AWS Foundations Benchmark

  • πŸ›‘οΈ NIST 800-53

  • πŸ“‹ SOC 2, GDPR, HIPAA

  • πŸ“œ Custom YAML/Python-based policies

πŸ“‹ Output includes:

  • Passed checks

  • Failed checks (with severity)

  • Skipped (manually ignored) checks

  • Suggestions and remediation tips

πŸ›  Command to Run:

  checkov -d .   
Enter fullscreen mode Exit fullscreen mode

πŸ”Ž Sample Check:

resource "aws_s3_bucket" "public_logs" {
  bucket = "my-bucket"
  acl    = "public-read"  # checkov will flag this!
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Bonus:

  • Shows passed βœ…, failed ❌, skipped ⚠️

  • Works with multiple IaC formats: Terraform, CloudFormation, K8s YAML

πŸ” GitHub Actions Workflow – Plug & Play

Add to .github/workflows/terraform-checks.yml:

name: Terraform Static Checks

on: [push, pull_request]

jobs:
  terraform-lint-secure:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2

      - name: Install Security Tools
        run: |
          curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
          curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install.sh | bash
          pip install checkov

      - name: Run tflint
        run: |
          tflint --init
          tflint

      - name: Run tfsec
        run: tfsec .

      - name: Run checkov
        run: checkov -d .

Enter fullscreen mode Exit fullscreen mode

πŸ’‘ You can make these blocking steps by using GitHub's branch protection rules!

βœ… Optional: Add continue-on-error: false to fail the PR if checks don’t pass.

πŸ” Example of tfsec scanning output:

Run aquasecurity/tfsec-action@v1.0.0
/usr/bin/docker run --name febf3299d148674848491e8a8d0b99f8e1aeba_ab881b --label febf32 --workdir /github/workspace --rm -e "AWS_ACCESS_KEY_ID" -e "AWS_SECRET_ACCESS_KEY" -e "TERRAFORM_CLI_PATH" -e "INPUT_WORKING_DIRECTORY" -e "INPUT_VERSION" -e "INPUT_FORMAT" -e "INPUT_ADDITIONAL_ARGS" -e "INPUT_SOFT_FAIL" -e "HOME" -e "GITHUB_JOB" -e "GITHUB_REF" -e "GITHUB_SHA" -e "GITHUB_REPOSITORY" -e "GITHUB_REPOSITORY_OWNER" -e "GITHUB_REPOSITORY_OWNER_ID" -e "GITHUB_RUN_ID" -e "GITHUB_RUN_NUMBER" -e "GITHUB_RETENTION_DAYS" -e "GITHUB_RUN_ATTEMPT" -e "GITHUB_ACTOR_ID" -e "GITHUB_ACTOR" -e "GITHUB_WORKFLOW" -e "GITHUB_HEAD_REF" -e "GITHUB_BASE_REF" -e "GITHUB_EVENT_NAME" -e "GITHUB_SERVER_URL" -e "GITHUB_API_URL" -e "GITHUB_GRAPHQL_URL" -e "GITHUB_REF_NAME" -e "GITHUB_REF_PROTECTED" -e "GITHUB_REF_TYPE" -e "GITHUB_WORKFLOW_REF" -e "GITHUB_WORKFLOW_SHA" -e "GITHUB_REPOSITORY_ID" -e "GITHUB_TRIGGERING_ACTOR" -e "GITHUB_WORKSPACE" -e "GITHUB_ACTION" -e "GITHUB_EVENT_PATH" -e "GITHUB_ACTION_REPOSITORY" -e "GITHUB_ACTION_REF" -e "GITHUB_PATH" -e "GITHUB_ENV" -e "GITHUB_STEP_SUMMARY" -e "GITHUB_STATE" -e "GITHUB_OUTPUT" -e "RUNNER_OS" -e "RUNNER_ARCH" -e "RUNNER_NAME" -e "RUNNER_ENVIRONMENT" -e "RUNNER_TOOL_CACHE" -e "RUNNER_TEMP" -e "RUNNER_WORKSPACE" -e "ACTIONS_RUNTIME_URL" -e "ACTIONS_RUNTIME_TOKEN" -e "ACTIONS_CACHE_URL" -e "ACTIONS_RESULTS_URL" -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/tictocgame-app/tictocgame-app":"/github/workspace" febf32:99d148674848491e8a8d0b99f8e1aeba
+ TFSEC_VERSION=latest
+ '[' latest '!=' latest ']'
++ wget -q https://api.github.com/repos/aquasecurity/tfsec/releases/latest -O -
++ grep -o -E 'https://.+?tfsec-linux-amd64'
++ head -n1
+ wget -O - -q https://github.com/aquasecurity/tfsec/releases/download/v1.28.14/tfsec-linux-amd64
+ install tfsec /usr/local/bin/
+ '[' -n /github/workspace ']'
+ cd /github/workspace
+ '[' -n '' ']'
+ '[' -n '' ']'
+ FORMAT=default
+ tfsec --format=default .

======================================================
tfsec is joining the Trivy family

tfsec will continue to remain available 
for the time being, although our engineering 
attention will be directed at Trivy going forward.

You can read more here: 
https://github.com/aquasecurity/tfsec/discussions/1994
======================================================
  timings
  ──────────────────────────────────────────
  disk i/o             14.317Β΅s
  parsing              154.272Β΅s
  adaptation           81.464Β΅s
  checks               2.895977ms
  total                3.14603ms

  counts
  ──────────────────────────────────────────
  modules downloaded   0
  modules processed    1
  blocks processed     3
  files read           1

  results
  ──────────────────────────────────────────
  passed               1
  ignored              0
  critical             0
  high                 0
  medium               0
  low                  0


No problems detected!

Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Sample main.tf to Try Locally

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "my_bucket" {
  bucket = "public-test-bucket"
  acl    = "public-read"
}

resource "aws_security_group" "bad_sg" {
  ingress {
    from_port   = 22
    to_port     = 22
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Enter fullscreen mode Exit fullscreen mode

Run all tools:

tflint
tfsec .
checkov -d .

Enter fullscreen mode Exit fullscreen mode

πŸ“ˆ Why This Workflow Matters - 3 Layers of Protection

Tool Role Key Value
βœ… tflint Linting & Best Practices Clean, typo-free code
πŸ” tfsec Security Vulnerability Scans Detects open/public resources
πŸ›‘ checkov Compliance & Policy Checks Enforces standards like CIS/NIST

Together, they:

βœ… Prevent misconfigurations
πŸ”’ Shift security left
πŸ“œ Ensure compliance by design

Image description

πŸ“Š Real-World Wins After Adding These Tools

βœ”οΈ Fewer post-merge hotfixes
βœ”οΈ Better security posture out-of-the-box
βœ”οΈ Faster reviews: Devs catch issues before PR
βœ”οΈ Happier security & compliance teams
βœ”οΈ Infrastructure that’s ready for audit

πŸš€ Closing Thoughts

Every terraform plan should feel like a final step, not a gamble.

Start treating your infrastructure as real production code β€” because it is.

πŸ›  Use tflint, tfsec, and checkov in every PR.

πŸ’¬ How do you secure your Terraform deployments?Let’s share tips in the comments πŸ‘‡

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (1)

Collapse
 
ankurk91 profile image
Ankur K β€’

Create a simple OTP system with AWS Serverless cover image

Create a simple OTP system with AWS Serverless

Implement a One Time Password (OTP) system with AWS Serverless services including Lambda, API Gateway, DynamoDB, Simple Email Service (SES), and Amplify Web Hosting using VueJS for the frontend.

Read full post

AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❀️