Terraform Security Scanning with tflint, Checkov and Trivy

· 2 min read · Terraform & OpenTofu Tutorials

Static analysis for infrastructure code #

Reviewing a plan by eye does not catch everything: an S3 bucket without encryption, a security group open to 0.0.0.0/0 on port 22 or an unencrypted RDS volume are valid configurations that validate accepts. Static analysis tools read the code (or the plan) and report them before deployment. They are part of the testing pipeline.

tflint: errors and conventions #

tflint is a linter. With the AWS ruleset it detects invalid values that only fail at apply time (a wrong instance type, a deprecated argument) and enforces conventions such as documented variables.

.tflint.hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"
}

plugin "aws" {
  enabled = true
  version = "0.38.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}
$ tflint --init
$ tflint --recursive

Check the plugin repository for the current version number.

Checkov: policies for many clouds #

Checkov has hundreds of built-in policies for AWS, Azure, GCP and Kubernetes, mapped to benchmarks such as CIS.

$ pip install checkov
$ checkov -d . --framework terraform

Typical output:

Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
	FAILED for resource: aws_s3_bucket.logs
	File: /main.tf:1-4

Trivy: misconfigurations, secrets and more #

Trivy (which includes the former tfsec checks) scans configuration, container images, dependencies and leaked secrets with one tool:

$ trivy config .
$ trivy config --severity HIGH,CRITICAL --exit-code 1 .

--exit-code 1 makes the command fail when it finds issues of that severity, which is what a CI job needs.

Scanning the plan #

Scanning the plan catches problems with values that are only known after evaluation (variables, modules):

$ tofu plan -out=tfplan
$ tofu show -json tfplan > tfplan.json
$ checkov -f tfplan.json

Handling false positives #

Not every finding applies. Suppress it where it happens and explain why, so the decision is visible in code review:

main.tf
resource "aws_s3_bucket" "public_site" {
  bucket = "ditwl-public-site"

  #checkov:skip=CKV_AWS_18:Access logs are not needed for a public static site
}

Trivy uses #trivy:ignore:AVD-AWS-0089. Avoid global ignores: they hide future real problems.

In CI #

.github/workflows/security.yml
name: security
on: pull_request

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: config
          severity: HIGH,CRITICAL
          exit-code: "1"

Pin actions to a version or commit in production. See the complete GitHub Actions pipeline.

Which one to choose #

Start with Trivy or Checkov, plus tflint. They overlap a lot, so running all the scanners adds noise. Add a policy engine such as OPA or Sentinel only when you need custom organization rules. Do not forget secrets management: scanners also find hard-coded credentials.

#Terraform #OpenTofu #Security #AWS