Terraform Security Scanning with tflint, Checkov and Trivy
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.
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:
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 #
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.