Most infrastructure security problems aren't novel attack vectors — they're misconfigurations that have been understood for years. S3 buckets with public ACLs. Security groups with 0.0.0.0/0 ingress on port 22. RDS instances without encryption at rest. EKS nodes running as root. These patterns appear in production infrastructure across thousands of organizations, often because the engineer who wrote the Terraform had no way of knowing the configuration was problematic.
Checkov is a static analysis tool for infrastructure-as-code. It reads your Terraform, CloudFormation, Kubernetes YAML, Dockerfile, Bicep, or Helm charts and checks them against a library of security and compliance rules before anything gets deployed. This guide explains how Checkov works, what it actually checks, how to run it effectively, and how to integrate it into a CI/CD pipeline.
How Checkov works
Checkov performs static analysis — it reads your IaC files and evaluates rules against the configuration without actually deploying anything or connecting to a cloud account. For Terraform, it reads .tf files directly (not state files or plan output) and evaluates the configuration values you've written.
Each check is a Python class or YAML specification that evaluates one or more properties of a resource. A check for S3 bucket encryption, for example, reads the server_side_encryption_configuration block of an aws_s3_bucket resource and verifies it's present and configured with AES-256 or aws:kms. If the block is absent or incorrectly configured, the check fails.
The output lists each check result against each resource:
$ checkov -d . Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled" FAILED for resource: aws_s3_bucket.data_lake File: /main.tf:1-12 Check: CKV_AWS_52: "Ensure S3 bucket has MFA delete enabled" FAILED for resource: aws_s3_bucket.data_lake Check: CKV_AWS_145: "Ensure that S3 buckets are encrypted with KMS by default" PASSED for resource: aws_s3_bucket.data_lake
The check ID format (CKV_AWS_18) is consistent — you can reference it in skip annotations, baseline files, and CI configuration.
Security frameworks and check sources
Checkov's checks come from multiple sources:
CIS Benchmarks — The Center for Internet Security publishes hardening guides for cloud platforms. The CIS AWS Foundations Benchmark, for example, specifies requirements like multi-region CloudTrail logging, MFA on root accounts, and password policy requirements. Checkov implements many CIS controls as checks. When you see CKV_AWS_* checks, many of them correspond to specific CIS benchmark controls.
NIST frameworks — Some checks map to NIST 800-53 and NIST CSF controls, used in US federal compliance requirements.
SOC 2 — Checks relevant to Trust Services Criteria (security, availability, confidentiality). Useful for organizations pursuing SOC 2 Type II certification.
PCI DSS — Payment Card Industry Data Security Standard controls for organizations handling payment card data.
Palo Alto's research — Many checks were developed by Palo Alto Networks' Prisma Cloud team (which acquired Bridgecrew, the original Checkov creator) based on real-world incident patterns.
You can filter checks by framework:
checkov -d . --framework terraform --check-type cis_aws checkov -d . --framework kubernetes
Running Checkov
Install and basic scan
# Install via pip pip install checkov # Scan a directory of Terraform files checkov -d ./infrastructure # Scan a single file checkov -f main.tf # Output JSON for programmatic processing checkov -d . -o json > checkov-results.json # Output SARIF format (for GitHub Code Scanning integration) checkov -d . -o sarif > checkov-results.sarif
Targeting specific frameworks
# Terraform only checkov -d . --framework terraform # Kubernetes only checkov -d . --framework kubernetes # Multiple frameworks checkov -d . --framework terraform --framework kubernetes # CloudFormation checkov -d . --framework cloudformation
Skipping specific checks
Not every check applies to every organization. You can skip checks globally or per-resource. Per-resource skips are inline annotations:
resource "aws_s3_bucket" "logs" {
bucket = "my-logs-bucket"
# checkov:skip=CKV_AWS_18:This bucket IS the access log destination
# checkov:skip=CKV_AWS_52:MFA delete not required for log buckets per policy INFRA-42
}
resource "aws_security_group_rule" "bastion_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
# checkov:skip=CKV_AWS_25:Bastion host intentionally allows SSH from internet
}
The comment after the colon in the skip annotation is required — it forces the engineer to document why the skip is justified. Undocumented skips are caught by Checkov itself when you run with --skip-check-comments validation.
Global skips go on the command line:
checkov -d . --skip-check CKV_AWS_52,CKV_AWS_144
Baseline files
A baseline file records the current set of failures so that CI only fails on new findings, not pre-existing ones. This is useful for brownfield codebases:
# Generate a baseline from current failures checkov -d . --create-baseline # On subsequent runs, only new failures (not in baseline) fail the check checkov -d . --baseline .checkov.baseline
Common checks and what they mean
S3
- CKV_AWS_18: Access logging not enabled. S3 can log all GET/PUT requests to a separate bucket. Required for audit trails in regulated environments.
- CKV_AWS_19: Server-side encryption not enabled. S3 data should be encrypted at rest with AES-256 or KMS.
- CKV_AWS_20: Bucket doesn't block public ACLs. Public ACLs are a common source of data exposure incidents.
- CKV2_AWS_62: Event notifications not enabled. Notifications for object creation/deletion are useful for detecting unauthorized access patterns.
EC2 and security groups
- CKV_AWS_25: Security group allows ingress from 0.0.0.0/0 on SSH (port 22). Direct SSH access from the internet should be replaced with SSM Session Manager or a bastion with restricted CIDR.
- CKV_AWS_24: Security group allows ingress from 0.0.0.0/0 on RDP (port 3389).
- CKV_AWS_8: EC2 instance created without IMDSv2 enforcement. IMDSv2 prevents SSRF attacks from reaching the instance metadata endpoint.
- CKV_AWS_135: EC2 instance EBS volume not encrypted.
RDS
- CKV_AWS_17: RDS publicly accessible. Databases should not be directly reachable from the internet.
- CKV_AWS_16: RDS not encrypted at rest.
- CKV_AWS_133: RDS instance deletion protection not enabled. Prevents accidental
terraform destroyor misconfigured automation from dropping production databases. - CKV_AWS_129: RDS cluster does not have logging enabled (audit, error, slowquery logs).
IAM
- CKV_AWS_40: IAM policy attached directly to a user (should attach to a group or role instead).
- CKV_AWS_1: IAM policy allows
*on all resources with admin actions. Overly broad policies. - CKV_AWS_274: IAM policy allows
*actions — wildcard actions on any resource.
Kubernetes
- CKV_K8S_6: Container running as root (
runAsNonRootnot set to true). - CKV_K8S_28: Container has no CPU or memory limits.
- CKV_K8S_30: Container has no read-only root filesystem.
- CKV_K8S_43: Image tag is
latestor missing — leads to non-reproducible deployments.
Remediating findings
Checkov output tells you what failed but not always how to fix it. The pattern for most remediations:
Missing encryption at rest (S3, RDS, EBS):
# S3 encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
}
}
# RDS encryption (set at creation — cannot be added to existing instance)
resource "aws_db_instance" "main" {
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
# ...
}
Security group overly permissive:
# Before (fails CKV_AWS_25)
resource "aws_security_group_rule" "ssh" {
cidr_blocks = ["0.0.0.0/0"]
from_port = 22
to_port = 22
# ...
}
# After — restrict to specific CIDR or use SSM instead
resource "aws_security_group_rule" "ssh" {
cidr_blocks = ["10.0.0.0/8"] # internal only
from_port = 22
to_port = 22
# ...
}
IMDSv2 enforcement:
resource "aws_instance" "app" {
metadata_options {
http_endpoint = "enabled"
http_tokens = "required" # enforces IMDSv2
http_put_response_hop_limit = 1
}
# ...
}
CI/CD integration
Checkov integrates into any CI system. The key decision: fail the pipeline on findings, or report them without blocking. Most teams start by reporting without blocking (to understand the current state), then enforce a zero-new-findings policy using baselines.
# GitHub Actions example
name: Security Scan
on: [pull_request]
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
soft_fail: false # fail the job on findings
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: checkov.sarif
The SARIF upload sends results to GitHub Code Scanning, where findings appear as annotations on the PR diff — each finding is shown inline on the exact line of Terraform that caused it. This makes results easier to act on than reading terminal output.
When a Checkov scan returns a list of failing resources by name, it can be useful to see those resources in context — where they sit in the architecture, what connects to them. InfraSketch supports overlaying Checkov JSON results onto an architecture diagram: paste your Terraform into InfraSketch, generate the diagram, then paste the Checkov JSON output to highlight failing resources with their check counts directly on the diagram.
Custom checks
Checkov supports custom checks in Python or YAML for organization-specific policies that aren't in the built-in library:
# custom_checks/enforce_required_tags.yaml
---
metadata:
name: Ensure required tags are present
id: CKV_CUSTOM_1
category: GENERAL_SECURITY
scope:
provider: terraform
definition:
and:
- cond_type: attribute
resource_types: [aws_instance, aws_rds_instance, aws_eks_cluster]
attribute: tags.Environment
operator: exists
- cond_type: attribute
resource_types: [aws_instance, aws_rds_instance, aws_eks_cluster]
attribute: tags.Team
operator: exists
checkov -d . --external-checks-dir ./custom_checks
YAML-based checks cover most use cases without writing Python. Python checks are needed for logic that requires evaluating multiple resources or complex conditions.