Week 3 — Day 17: IaC Scanning with Checkov & tfsec
A full walkthrough of Checkov and tfsec — scanning Terraform, CloudFormation, and Kubernetes manifests for security misconfigurations before they reach production.
Why IaC Security Scanning?
Infrastructure as Code defines your cloud environment — security groups, IAM policies, S3 bucket settings, encryption configs. A misconfiguration in Terraform is a misconfiguration in production.
IaC scanning catches these before terraform apply — in the PR, in the pipeline, before any real infrastructure changes.
Common IaC misconfigurations:
- Security groups allowing
0.0.0.0/0on port 22 or 3389 - S3 buckets with public access enabled
- RDS instances without encryption
- EC2 instances with public IPs
- IAM roles with wildcard permissions
- Unencrypted EBS volumes
- CloudTrail disabled
- Logging not enabled on load balancers
Checkov
Checkov is a static analysis tool for IaC by Bridgecrew (Palo Alto). It supports Terraform, CloudFormation, Kubernetes, Helm, Dockerfile, GitHub Actions, and more.
Installation
1
2
3
4
pip install checkov
# Verify
checkov --version
Scanning Terraform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Scan a Terraform directory
checkov -d ./terraform/
# Scan a single file
checkov -f main.tf
# Show only failed checks
checkov -d ./terraform/ --compact
# Output as JSON
checkov -d ./terraform/ --output json > results.json
# Output as JUnit XML (for CI test reports)
checkov -d ./terraform/ --output junitxml > results.xml
[SCREENSHOT]— Terminal showing checkov -d ./terraform/ output — a table of passed/failed checks with check IDs (CKV_AWS_), resource names, and file paths. Summary at the bottom showing X passed, Y failed*
Understanding Checkov Output
1
2
3
4
5
6
7
8
9
10
11
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.data-bucket
File: /terraform/s3.tf:12-28
Guide: https://docs.bridgecrew.io/docs/s3_14
Check: CKV_AWS_21: "Ensure the S3 bucket has versioning enabled"
PASSED for resource: aws_s3_bucket.data-bucket
Check: CKV_AWS_145: "Ensure that S3 buckets are encrypted with KMS by default"
FAILED for resource: aws_s3_bucket.data-bucket
File: /terraform/s3.tf:12-28
Each check has:
- Check ID — e.g.,
CKV_AWS_18— uniquely identifies the rule - Status — PASSED / FAILED / SKIPPED
- Resource — the Terraform resource that was checked
- File + line — exactly where in your code
[SCREENSHOT]— Checkov output showing a mix of PASSED (green) and FAILED (red) checks on Terraform resources, with the check IDs and resource names visible
Key Checkov Checks for AWS
| Check ID | What it verifies |
|---|---|
CKV_AWS_18 | S3 access logging enabled |
CKV_AWS_19 | S3 server-side encryption enabled |
CKV_AWS_20 | S3 bucket not publicly readable |
CKV_AWS_21 | S3 versioning enabled |
CKV_AWS_23 | ALB access logs enabled |
CKV_AWS_25 | Security group no unrestricted SSH |
CKV_AWS_57 | S3 block public ACLs |
CKV_AWS_66 | CloudTrail log file validation |
CKV_AWS_76 | CloudTrail S3 bucket has access logging |
CKV_AWS_86 | ECS task definition logging enabled |
CKV_AWS_97 | S3 bucket encryption with KMS |
CKV_AWS_111 | IAM policy no wildcard resources |
CKV_AWS_130 | VPC subnets no public IP on launch |
Scanning Your MindCraft Terraform
1
2
3
4
5
# Navigate to the MindCraft Terraform directory
checkov -d _posts/MindCraft\ Cloud\ Deployment/ --filter-regex ".*\.tf"
# Or scan any specific Terraform directory
checkov -d ./terraform/ --check CKV_AWS_20,CKV_AWS_19,CKV_AWS_25
[SCREENSHOT]— Checkov running on the MindCraft Terraform code showing the specific failures — e.g., S3 bucket without encryption, security group open to 0.0.0.0/0, with the exact Terraform file and line numbers
Skipping False Positives
Add a skip comment directly in Terraform:
1
2
3
4
5
6
resource "aws_s3_bucket" "public-assets" {
bucket = "my-public-website-assets"
# checkov:skip=CKV_AWS_20:Public assets bucket intentionally public for website
# checkov:skip=CKV_AWS_57:Public ACLs required for static website hosting
}
Or skip via CLI:
1
checkov -d . --skip-check CKV_AWS_20,CKV_AWS_57
Scanning Kubernetes Manifests
1
checkov -d ./k8s/ --framework kubernetes
Key Kubernetes checks:
| Check ID | What it verifies |
|---|---|
CKV_K8S_6 | Do not admit root containers |
CKV_K8S_8 | Liveness probe configured |
CKV_K8S_14 | Image tag is not latest |
CKV_K8S_20 | Containers do not run with allowPrivilegeEscalation |
CKV_K8S_28 | Do not admit containers with added capabilities |
CKV_K8S_30 | Do not admit containers with NET_RAW capability |
CKV_K8S_37 | Minimize the admission of containers with added capability |
[SCREENSHOT]— Checkov scanning a Kubernetes deployment manifest and showing failures like “Do not admit root containers” and “Image tag is not latest” with the manifest file and line numbers
Scanning Dockerfiles
1
checkov -f Dockerfile --framework dockerfile
[SCREENSHOT]— Checkov scanning a Dockerfile and flagging issues like “Ensure that a user for the container has been created” (non-root user) and “Ensure that COPY is used instead of ADD”
tfsec
tfsec is a Terraform-specific security scanner by Aqua Security. It’s faster than Checkov for pure Terraform use cases and integrates natively with the Terraform ecosystem.
Installation
1
2
3
4
5
6
7
# Windows via Scoop
scoop install tfsec
# Or download from GitHub releases
# https://github.com/aquasecurity/tfsec/releases
tfsec --version
Scanning with tfsec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Scan current directory
tfsec .
# Scan specific directory
tfsec ./terraform/
# Only show specific severities
tfsec . --minimum-severity HIGH
# Output as JSON
tfsec . --format json --out results.json
# Output as SARIF
tfsec . --format sarif --out results.sarif
[SCREENSHOT]— Terminal showing tfsec . output — colored blocks for each finding with the check ID, description, severity (CRITICAL/HIGH/MEDIUM/LOW), file path, line range, and a code snippet of the offending Terraform resource
tfsec Output Format
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Result #1 HIGH Security group rule allows ingress from public internet.
───────────────────────────────────────────────
ID aws-ec2-no-public-ingress-sgr
Impact Your port exposed to the internet
Resolution Remove the public CIDR block from the rule
More Info https://aquasecurity.github.io/tfsec/...
───────────────────────────────────────────────
/terraform/security-groups.tf:15-22
via /terraform/security-groups.tf:12-25
───────────────────────────────────────────────
12 resource "aws_security_group_rule" "ssh" {
13 type = "ingress"
14 from_port = 22
15 [ cidr_blocks = ["0.0.0.0/0"]
16 to_port = 22
17 protocol = "tcp"
18 }
[SCREENSHOT]— tfsec output showing a security group with 0.0.0.0/0 on port 22, with the code snippet highlighted showing exactly which line is the problem
Ignoring Findings in tfsec
1
2
3
4
5
6
7
resource "aws_security_group_rule" "bastion-ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # tfsec:ignore:aws-ec2-no-public-ingress-sgr - bastion host, intentional
}
Checkov vs tfsec
| Checkov | tfsec | |
|---|---|---|
| Supports | Terraform, CF, K8s, Dockerfile, GH Actions | Terraform only |
| Speed | Moderate | Fast |
| Custom rules | Python or YAML | YAML |
| Output formats | JSON, JUnit, SARIF, CLI | JSON, SARIF, CLI |
| Best for | Multi-framework pipelines | Pure Terraform-heavy teams |
Use both in the pipeline — they have overlapping but not identical rule sets and catch different things.
GitHub Actions Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# .github/workflows/iac-scan.yml
name: IaC Security Scan
on:
push:
paths:
- 'terraform/**'
- '*.tf'
pull_request:
paths:
- 'terraform/**'
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
soft_fail: false # fail the pipeline on findings
- name: Upload Checkov SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: checkov.sarif
tfsec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform/
minimum_severity: HIGH
format: sarif
sarif_file: tfsec.sarif
- name: Upload tfsec SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: tfsec.sarif
[SCREENSHOT]— GitHub Actions run showing both Checkov and tfsec jobs — one passing (green) and one failing (red) due to a misconfigured security group, with the finding details in the job output
[SCREENSHOT]— GitHub PR showing a “Security scanning / checkov” check failing with a link to the details, blocking the merge
Lab — Scan MindCraft Terraform Code
Objective: Run Checkov and tfsec on your existing MindCraft infrastructure code and review findings.
- Navigate to your Terraform directory:
1
cd "d:/Mhdomer_logs/mhdomer.github.io/_posts/MindCraft Cloud Deployment"
- Run Checkov:
1
checkov -d . --compact --framework terraform
[SCREENSHOT]— Checkov output showing failed checks on the MindCraft Terraform code with check IDs and resource names
- Run tfsec:
1
tfsec . --minimum-severity MEDIUM
[SCREENSHOT]— tfsec output on the MindCraft Terraform code showing findings with code snippets
- Pick 2-3 failing checks → open the Terraform file → apply the fix:
Example fix — enable S3 bucket encryption:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Before
resource "aws_s3_bucket" "app-data" {
bucket = "mindcraft-app-data"
}
# After
resource "aws_s3_bucket" "app-data" {
bucket = "mindcraft-app-data"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "app-data" {
bucket = aws_s3_bucket.app-data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
- Re-run Checkov — confirm the fixed checks now pass
[SCREENSHOT]— Checkov output after the fix showing the previously failed S3 encryption check now marked as PASSED
Key Takeaways
- IaC scanning runs before
terraform apply— fix misconfigs before they become real infrastructure - Checkov covers Terraform + Kubernetes + Dockerfile + CloudFormation — use it as the default
- tfsec is faster and Terraform-focused — run both for maximum coverage
- Inline skip comments with justifications for intentional exceptions — don’t just ignore findings
- Trigger IaC scans only on changes to
.tffiles to keep the pipeline fast - SARIF output + GitHub Security tab = persistent tracking of IaC findings across PRs

