Infra terraform security scan
Skill ivanshamaev/de-agent-skills/group_skills/infra_dataops_group_skills/infra_terraform_security_scan
Terraform security scanning — tfsec static analysis (AWS/GCP/Azure misconfigurations), Checkov IaC policy checks (750+ rules, CIS benchmarks), tflint security rules, KICS scanner, S3 encryption/public access/versioning checks, IAM least-privilege patterns, security group rule review (0.0.0.0/0), KMS key rotation, VPC flow logs, CloudTrail enabled, pre-commit hooks, SARIF output for GitHub Security tab, policy-as-code with OPA/SentinelFrom its SKILL.md
npx -y skills add ivanshamaev/de-agent-skills --skill infra_terraform_security_scanAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.0 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
Terraform Security Scan
When to Use
- Security review of a Terraform PR before merge
- Compliance assessment against CIS Benchmarks (AWS/GCP/Azure)
- Auditing existing infrastructure for misconfigurations
- Setting up security gates in CI/CD pipelines
- Establishing policy-as-code for IaC governance
tfsec — Static Analysis
Installation and Basic Usage
# Install
brew install tfsec # macOS
curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
# Scan current directory
tfsec .
# Scan with specific output format
tfsec . --format json | jq '.results[] | {rule: .rule_id, severity: .severity, description: .description}'
# SARIF output for GitHub Security tab
tfsec . --format sarif --out tfsec-results.sarif
# Include tfvars values in analysis
tfsec . --tfvars-file terraform.tfvars
# Ignore low-severity findings
tfsec . --minimum-severity HIGH
Inline Suppressions
resource "aws_s3_bucket" "public_assets" {
bucket = "my-company-public-assets"
#tfsec:ignore:aws-s3-no-public-buckets
#tfsec:ignore:aws-s3-block-public-acls
tags = local.common_tags
}
Config File
# .tfsec/config.yaml
minimum_severity: MEDIUM
exclude:
- aws-s3-no-public-buckets # intentionally public CDN bucket
Checkov — 750+ Policy Checks
Installation and Usage
# Install
pip install checkov
# Scan Terraform directory
checkov -d . --framework terraform
# Scan with specific checks only (CIS AWS benchmark)
checkov -d . --check CKV_AWS_1,CKV_AWS_2 # specific checks
checkov -d . --bc-api-key $BRIDGECREW_API_KEY # with Bridgecrew cloud
# Output formats
checkov -d . -o json > checkov-results.json
checkov -d . -o sarif > checkov-results.sarif
# Soft-fail: report findings but don't fail the pipeline
checkov -d . --soft-fail
# Skip specific checks
checkov -d . --skip-check CKV_AWS_79,CKV_AWS_91
Key Terraform Checks (AWS)
| Check ID | Description |
|---|---|
CKV_AWS_18 | S3 bucket access logging enabled |
CKV_AWS_19 | S3 bucket encryption enabled |
CKV_AWS_20 | S3 bucket not publicly accessible |
CKV_AWS_21 | S3 versioning enabled |
CKV_AWS_52 | S3 MFA delete enabled |
CKV_AWS_2 | ALB/ELB HTTPS listeners only |
CKV_AWS_79 | EC2 IMDSv2 enforced |
CKV_AWS_135 | EC2 no public IP at launch |
CKV_AWS_17 | RDS not publicly accessible |
CKV_AWS_16 | RDS encryption at rest |
CKV_AWS_23 | RDS backup retention >= 7 days |
CKV_AWS_25 | Security group no unrestricted SSH |
CKV_AWS_24 | Security group no unrestricted RDP |
CKV_AWS_36 | CloudTrail logging enabled |
CKV_AWS_86 | CloudFront logging enabled |
Common Security Patterns to Enforce
S3 Hardening
# ✅ Correct: fully hardened S3 bucket
resource "aws_s3_bucket" "data_lake" {
bucket = "${local.name_prefix}-data-lake"
tags = local.common_tags
}
resource "aws_s3_bucket_versioning" "data_lake" {
bucket = aws_s3_bucket.data_lake.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data_lake" {
bucket = aws_s3_bucket.data_lake.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.data_lake.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "data_lake" {
bucket = aws_s3_bucket.data_lake.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_logging" "data_lake" {
bucket = aws_s3_bucket.data_lake.id
target_bucket = aws_s3_bucket.access_logs.id
target_prefix = "data-lake/"
}
KMS Key with Rotation
resource "aws_kms_key" "data_lake" {
description = "KMS key for data lake S3 encryption"
enable_key_rotation = true # CKV_AWS_7: rotation required
deletion_window_in_days = 30
tags = local.common_tags
}
resource "aws_kms_alias" "data_lake" {
name = "alias/${local.name_prefix}-data-lake"
target_key_id = aws_kms_key.data_lake.key_id
}
Security Groups — No 0.0.0.0/0
# ❌ Fails CKV_AWS_25 — unrestricted SSH
resource "aws_security_group_rule" "bad_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # never do this
}
# ✅ Correct — restrict to VPC CIDR or specific source SG
resource "aws_security_group_rule" "bastion_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion.id
security_group_id = aws_security_group.workers.id
}
IAM Least Privilege
# ❌ Overly broad
data "aws_iam_policy_document" "bad" {
statement {
actions = ["s3:*"]
resources = ["*"]
}
}
# ✅ Scoped to specific bucket and actions
data "aws_iam_policy_document" "airflow_s3" {
statement {
sid = "ReadDags"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = [
aws_s3_bucket.dags.arn,
"${aws_s3_bucket.dags.arn}/*",
]
}
statement {
sid = "WriteLogs"
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.logs.arn}/airflow/*"]
}
}
RDS Security
resource "aws_db_instance" "main" {
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
# Security requirements
publicly_accessible = false # CKV_AWS_17
storage_encrypted = true # CKV_AWS_16
kms_key_id = aws_kms_key.rds.arn
backup_retention_period = 7 # CKV_AWS_23 — >= 7 days
deletion_protection = true
skip_final_snapshot = false
# No default port in production
port = 5433
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
}
GitHub Actions CI Integration
name: Terraform Security Scan
on:
pull_request:
paths:
- "**.tf"
jobs:
tfsec:
runs-on: ubuntu-latest
permissions:
security-events: write # for SARIF upload
steps:
- uses: actions/checkout@v4
- name: tfsec scan
uses: aquasecurity/[email protected]
with:
soft_fail: false
format: sarif
sarif_file: tfsec.sarif
- name: Upload tfsec SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: tfsec.sarif
checkov:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Checkov scan
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
skip_check: CKV_AWS_79 # IMDSv2 — covered by launch template
- name: Upload Checkov SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.92.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_tfsec
args:
- --args=--minimum-severity=HIGH
- id: terraform_checkov
args:
- --args=--skip-check CKV_AWS_79
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks # catch secrets before commit
OPA/Conftest Policy-as-Code
# policies/s3_encryption.rego
package terraform.s3
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
not input.resource.aws_s3_bucket_server_side_encryption_configuration[name]
msg := sprintf("S3 bucket '%s' does not have server-side encryption configured", [name])
}
deny[msg] {
resource := input.resource.aws_security_group_rule[name]
resource.cidr_blocks[_] == "0.0.0.0/0"
resource.from_port <= 22
resource.to_port >= 22
msg := sprintf("Security group rule '%s' allows SSH from 0.0.0.0/0", [name])
}
# Run conftest against Terraform plan JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json -p policies/
Anti-Patterns
- Skipping security scans in CI for "small changes" — misconfigurations are often in small changes; always scan on every PR.
- Mass-suppressing tfsec findings —
#tfsec:ignore:*hides real issues; document each suppression with a reason. - Not scanning modules separately — root module scan misses issues inside nested modules; use
--recursiveflag. - Failing CI without human review for warnings — MEDIUM findings block good PRs; set minimum severity to HIGH in blocking gate, report MEDIUM as warnings.
- Checking in tfvars with secrets —
.gitignoreterraform.tfvars files containing secrets; inject via env var or Vault.
References
- tfsec:
github.com/aquasecurity/tfsec - Checkov:
checkov.io - pre-commit-terraform:
github.com/antonbabenko/pre-commit-terraform - KICS:
checkmarx.com/blog/kics-open-source-iac-scanning/ - OPA Conftest:
conftest.dev - CIS AWS Terraform Benchmark:
cisecurity.org - Related skills:
[[infra-terraform-review]],[[infra-terraform-cost-estimator]],[[infra-kubernetes-security-audit]]
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.