Checkov and Terrascan: Policy-as-Code Security Testing for Terraform
A misconfigured S3 bucket. An RDS instance without encryption. A security group open to 0.0.0.0/0. These are the infrastructure mistakes that make headlines. Policy-as-code tools like Checkov and Terrascan catch them at write time — before terraform apply ever runs.
This guide explains how both tools work, how to integrate them into your workflow, and when to use one over the other.
The Problem: Terraform Is Powerful but Permissive
Terraform lets you write cidr_blocks = ["0.0.0.0/0"] without complaint. It creates what you tell it to create. There's no built-in security review, no guardrails against common misconfigurations. You could deploy publicly accessible databases, unencrypted storage, and unauthenticated APIs — all valid Terraform, all catastrophic security failures.
Policy-as-code tools solve this by treating security rules as code that runs against your Terraform configurations before deployment.
Checkov
Checkov is an open-source static analysis tool from Bridgecrew (now part of Palo Alto Networks). It ships with 1000+ built-in checks covering AWS, GCP, Azure, and Kubernetes.
Installation
pip install checkov
# or
brew install checkovBasic Usage
Point Checkov at your Terraform directory:
checkov -d ./terraform/Sample output:
Check: CKV_AWS_20: "Ensure the S3 bucket has access control list (ACL) is private"
PASSED for resource: aws_s3_bucket.logs
File: /terraform/s3.tf:1-20
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.app_data
File: /terraform/s3.tf:22-35
Guide: https://docs.bridgecrew.io/docs/s3_13-enable-logging
Passed checks: 47, Failed checks: 3, Skipped checks: 0Checkov exits with code 1 if any check fails — perfect for CI gating.
Scanning Specific File Types
# Terraform files only
checkov -d . --framework terraform
# Scan a single file
checkov -f main.tf
# Output as JSON for programmatic use
checkov -d . -o json > checkov-results.json
# Output as SARIF for GitHub Security tab
checkov -d . -o sarif > results.sarifUnderstanding Checks
Each check has a unique ID following the pattern CKV_<PROVIDER>_<NUMBER>. Common critical checks:
| Check ID | What It Catches |
|---|---|
| CKV_AWS_20 | S3 bucket with public ACL |
| CKV_AWS_57 | S3 bucket allows public policy |
| CKV_AWS_16 | RDS without encryption |
| CKV_AWS_17 | RDS publicly accessible |
| CKV_AWS_25 | Security group open SSH (22) to 0.0.0.0/0 |
| CKV_AWS_260 | Security group open HTTP (80) to 0.0.0.0/0 |
| CKV_AWS_79 | EC2 IMDSv1 enabled (prefer IMDSv2) |
| CKV_AWS_111 | IAM policy with wildcard * permissions |
Writing Custom Checks
When built-in checks don't cover your policies, write your own:
# checks/custom/s3_requires_kms.py
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class S3RequiresKMSEncryption(BaseResourceCheck):
def __init__(self):
name = "Ensure S3 bucket uses KMS encryption, not AES"
id = "CKV_CUSTOM_1"
supported_resources = ['aws_s3_bucket_server_side_encryption_configuration']
categories = [CheckCategories.ENCRYPTION]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
rules = conf.get("rule", [{}])
for rule in rules:
apply_config = rule.get("apply_server_side_encryption_by_default", [{}])
for config in apply_config:
sse_algo = config.get("sse_algorithm", [""])
if isinstance(sse_algo, list):
sse_algo = sse_algo[0]
if sse_algo == "aws:kms":
return CheckResult.PASSED
return CheckResult.FAILED
scanner = S3RequiresKMSEncryption()Run with custom checks:
checkov -d . --external-checks-dir ./checks/custom/Suppressing False Positives
Some checks don't apply to your context. Suppress them inline:
resource "aws_s3_bucket" "public_website" {
bucket = "my-public-website"
# checkov:skip=CKV_AWS_20:This bucket intentionally hosts a public static website
# checkov:skip=CKV_AWS_57:Public policy required for static website hosting
}Or suppress globally in a .checkov.yaml config:
skip-check:
- CKV_AWS_144 # Cross-region replication not required for dev environments
compact: true
quiet: falseTerrascan
Terrascan is an open-source tool from Tenable that focuses on cloud security and compliance. It supports Terraform, Kubernetes, Helm, Docker, and more.
Installation
# macOS
brew install terrascan
# Binary
curl -L "https://github.com/tenable/terrascan/releases/latest/download/terrascan_Linux_x86_64.tar.gz" | tar -xzBasic Usage
# Scan current directory
terrascan scan -t aws
# Scan specific Terraform files
terrascan scan -t aws -i terraform -d ./terraform/
# Output as JSON
terrascan scan -t aws -o jsonSample output:
Violation Details -
Description : Ensure S3 bucket is not publicly exposed
File : s3.tf
Module Name : root
Plan Root : ./
Line : 15
Severity : HIGH
Rule Name : s3BucketAuthenticatedUsers
Rule ID : AWS.S3.Data Protection.High.0404Terrascan Policies
Terrascan uses Rego (OPA policy language) for its rules:
# policies/deny_public_s3.rego
package accurics
#rule ID: AWS.S3.Data Protection.High.0404
s3BucketAuthenticatedUsers[retVal] {
bucket := input.aws_s3_bucket[_]
acl := bucket.config.acl
acl == "public-read"
retVal := {
"Id": bucket.id,
"Config": bucket.config
}
}Write custom policies in Rego and scan with them:
terrascan scan -t aws --policy-path ./custom-policies/Compliance Frameworks
Terrascan maps violations to compliance frameworks:
# See which CIS benchmarks are violated
terrascan scan -t aws -o json | jq '.results.violations[].rule_reference_id'Supported frameworks: CIS, GDPR, HIPAA, NIST, PCI-DSS, SOC 2.
This makes Terrascan particularly useful if you need compliance reporting — you can tell auditors exactly which CIS AWS Foundations benchmarks pass.
Checkov vs Terrascan: Side-by-Side
| Feature | Checkov | Terrascan |
|---|---|---|
| Custom rules language | Python | Rego (OPA) |
| Built-in checks | 1000+ | 500+ |
| Compliance mapping | ✅ | ✅ (stronger) |
| SARIF output | ✅ | ✅ |
| Terraform Plan support | ✅ | ✅ |
| Speed | Fast | Fast |
| Active development | High | Medium |
| Learning curve | Low (Python) | Higher (Rego) |
Use Checkov if:
- You want the most checks out of the box
- Your team knows Python for custom rules
- You want GitHub Security tab integration via SARIF
- You need Terraform, Kubernetes, and Dockerfile scanning in one tool
Use Terrascan if:
- You already use OPA/Rego elsewhere
- Compliance reporting is a primary requirement
- You need Terrascan's specific provider coverage
Many teams run both.
CI/CD Integration
GitHub Actions with Checkov
name: Security Scan
on: [pull_request]
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: results.sarif
soft_fail: false # fail the PR on violations
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
if: always()GitHub Actions with Terrascan
name: Terrascan
on: [pull_request]
jobs:
terrascan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Terrascan
uses: tenable/terrascan-action@main
with:
iac_type: terraform
iac_dir: terraform/
policy_type: aws
only_warn: false
sarif_upload: truePre-commit Hooks
Catch issues before they even reach CI:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/bridgecrewio/checkov
rev: '3.2.0'
hooks:
- id: checkov
args: ['--framework', 'terraform']pre-commit install
# Now checkov runs on every git commitScanning Terraform Plan Output
Scanning .tf files catches most issues, but some misconfigurations only appear after variable substitution. Scan the plan JSON for full coverage:
# Generate plan
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
# Scan the plan
checkov -f tfplan.json --file-type terraform_plan
terrascan scan -t aws -i terraform_plan -f tfplan.jsonThis catches dynamic values that can't be seen in static .tf analysis.
Baseline Workflow
- Start blocking only HIGH/CRITICAL — don't gate PRs on every medium check initially
- Fix violations in existing code — establish a clean baseline
- Enable SARIF upload to GitHub for developer visibility
- Suppress legitimate exceptions with inline comments explaining why
- Graduate to blocking medium once the team is used to the process
- Add custom checks for your organization's specific policies
# Only fail on HIGH and CRITICAL
checkov -d . --check-threshold HIGHSecurity misconfigurations in Terraform are preventable. Checkov and Terrascan turn your security policy into executable checks that run before any infrastructure is deployed. Add one to your CI pipeline today — it takes 10 minutes to set up and could prevent the breach that takes months to recover from.