npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- When provisioning cloud infrastructure with Terraform, CloudFormation, or Pulumi and needing automated security validation
- When compliance frameworks require evidence of infrastructure configuration review before deployment
- When preventing common cloud misconfigurations like public S3 buckets, open security groups, or unencrypted storage
- When establishing guardrails that block insecure infrastructure changes in pull requests
- When managing multi-cloud environments requiring consistent security policies across AWS, Azure, and GCP
Do not use for scanning application source code (use SAST), for monitoring already-deployed infrastructure drift (use cloud security posture management tools), or for container image vulnerability scanning (use Trivy).
Prerequisites
- Checkov v3.x installed (
pip install checkov) or tfsec installed - Terraform, CloudFormation, or Kubernetes IaC files in the repository
- CI/CD pipeline with access to IaC directories
- Bridgecrew API key (optional, for Checkov platform integration)
Workflow
Step 1: Run Checkov Against Terraform Files
# Scan all Terraform files in a directory
checkov -d ./terraform/ --framework terraform --output cli --output json --output-file-path ./results
# Scan specific file
checkov -f main.tf --output json
# Scan Terraform plan (more accurate for dynamic values)
terraform init && terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
checkov -f tfplan.json --framework terraform_plan
# Scan with specific checks only
checkov -d ./terraform/ --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_20
# Skip specific checks
checkov -d ./terraform/ --skip-check CKV_AWS_145,CKV2_AWS_6Step 2: Integrate IaC Scanning into GitHub Actions
# .github/workflows/iac-security.yml
name: IaC Security Scan
on:
pull_request:
paths:
- 'terraform/**'
- 'cloudformation/**'
- 'k8s/**'
jobs:
checkov:
name: Checkov IaC Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
output_format: cli,sarif
output_file_path: console,checkov.sarif
soft_fail: false
skip_check: CKV_AWS_145
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
category: checkov-iac
tfsec:
name: tfsec Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
working_directory: terraform/
sarif_file: tfsec.sarif
soft_fail: false
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: tfsec.sarif
category: tfsecStep 3: Create Custom Checkov Policies
# custom_checks/s3_versioning.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class S3BucketVersioning(BaseResourceCheck):
def __init__(self):
name = "Ensure S3 bucket has versioning enabled"
id = "CKV_CUSTOM_1"
supported_resources = ["aws_s3_bucket"]
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
versioning = conf.get("versioning", [{}])
if isinstance(versioning, list) and len(versioning) > 0:
if versioning[0].get("enabled", [False])[0]:
return CheckResult.PASSED
return CheckResult.FAILED
check = S3BucketVersioning()Step 4: Configure Baseline and Suppressions
# .checkov.yaml
branch: main
compact: true
directory:
- terraform/
- cloudformation/
framework:
- terraform
- cloudformation
- kubernetes
output:
- cli
- sarif
skip-check:
- CKV_AWS_145 # S3 default encryption with CMK (using SSE-S3 is acceptable)
- CKV2_AWS_6 # S3 bucket request logging (handled at CloudTrail level)
soft-fail: falseStep 5: Scan Kubernetes Manifests and Helm Charts
# Scan Kubernetes manifests
checkov -d ./k8s/ --framework kubernetes
# Scan Helm charts (renders templates first)
checkov -d ./charts/myapp/ --framework helm
# Scan with KICS (Keeping Infrastructure as Code Secure)
docker run -v $(pwd)/k8s:/path checkmarx/kics:latest scan \
--path /path \
--output-path /path/results \
--type Kubernetes \
--report-formats json,sarifKey Concepts
| Term | Definition |
|---|---|
| IaC Scanning | Automated analysis of infrastructure code templates to detect security misconfigurations before deployment |
| Policy as Code | Security policies defined as executable code that can be version-controlled, tested, and enforced automatically |
| CKV Check ID | Checkov's unique identifier for each security check (e.g., CKV_AWS_18 for S3 public access) |
| Terraform Plan Scanning | Scanning the resolved Terraform plan JSON which includes computed values and module expansions |
| Graph-based Scanning | Checkov's ability to analyze relationships between resources, not just individual resource configs |
| Drift Detection | Identifying differences between IaC definitions and actual deployed infrastructure state |
| Custom Policy | Organization-specific security checks authored in Python or YAML to enforce internal standards |
Tools & Systems
- Checkov: Open-source IaC scanner by Bridgecrew with 2500+ built-in policies covering major cloud providers
- tfsec: Terraform-focused static analysis tool by Aqua Security with deep HCL understanding
- KICS: Open-source IaC scanner by Checkmarx supporting 15+ IaC frameworks
- Terrascan: IaC scanner with OPA Rego policy support for custom policy authoring
- Snyk IaC: Commercial IaC scanner integrated with the Snyk platform
Common Scenarios
Scenario: Preventing Public S3 Buckets in Terraform
Context: A development team repeatedly creates S3 buckets without proper access controls. A recent incident exposed customer data through a public bucket.
Approach:
- Enable Checkov in the CI/CD pipeline for all Terraform changes
- Enforce CKV_AWS_18 (no public read ACL), CKV_AWS_19 (encryption), CKV_AWS_20 (no public access block disabled)
- Create a custom policy requiring the
aws_s3_bucket_public_access_blockresource for every S3 bucket - Set
soft_fail: falseto block PR merges when S3 security checks fail - Provide Terraform modules with security defaults that teams can reuse
Pitfalls: Scanning only .tf files misses dynamically computed values. Use Terraform plan scanning for higher accuracy. Checkov's resource-relationship checks (CKV2 prefix) require graph analysis mode.
Output Format
IaC Security Scan Report
==========================
Framework: Terraform
Directory: terraform/
Scan Date: 2026-02-23
Checkov Results:
Passed: 187
Failed: 12
Skipped: 3
Unknown: 0
FAILED CHECKS:
CKV_AWS_18 [HIGH] S3 Bucket has public read ACL
Resource: aws_s3_bucket.data_lake
File: terraform/storage.tf:15-28
CKV_AWS_24 [HIGH] CloudWatch log group not encrypted
Resource: aws_cloudwatch_log_group.app
File: terraform/monitoring.tf:3-8
CKV_AWS_79 [MEDIUM] Instance metadata service v1 enabled
Resource: aws_instance.web
File: terraform/compute.tf:12-30
QUALITY GATE: FAILED (2 HIGH severity findings)References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.3 KB
API Reference: Implementing Infrastructure as Code Security Scanning
Checkov CLI
# Scan Terraform directory
checkov -d /path/to/tf --framework terraform --output json
# Scan specific file
checkov -f main.tf
# Scan CloudFormation
checkov -d . --framework cloudformation
# Scan Kubernetes manifests
checkov -d . --framework kubernetes
# Skip specific checks
checkov -d . --skip-check CKV_AWS_18,CKV_AWS_21tfsec CLI
# Scan directory
tfsec /path/to/tf --format json
# Exclude specific rules
tfsec . --exclude aws-s3-enable-bucket-logging
# Minimum severity
tfsec . --minimum-severity HIGHCommon IaC Security Checks
| Check ID | Description | Severity |
|---|---|---|
| CKV_AWS_18 | S3 bucket logging | MEDIUM |
| CKV_AWS_19 | S3 bucket encryption | HIGH |
| CKV_AWS_23 | Security group open to 0.0.0.0/0 | HIGH |
| CKV_AWS_41 | RDS encryption | HIGH |
| CKV_AWS_145 | KMS key rotation | MEDIUM |
| CKV_K8S_1 | Pod privileged container | CRITICAL |
GitHub Actions Integration
- uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
soft_fail: falseReferences
- Checkov: https://www.checkov.io/
- tfsec: https://aquasecurity.github.io/tfsec/
- KICS: https://kics.io/
- Bridgecrew: https://www.bridgecrew.io/
standards.md1.8 KB
Standards Reference: IaC Security Scanning
CIS Cloud Benchmarks
CIS AWS Foundations Benchmark v3.0
- Maps directly to Checkov CKV_AWS_* checks
- Covers IAM, logging, monitoring, networking, and storage security
- Automated scanning validates 100+ benchmark controls
CIS Azure Foundations Benchmark v2.1
- Maps to Checkov CKV_AZURE_* checks
- Covers identity, security center, storage, database, and network controls
CIS GCP Foundations Benchmark v2.0
- Maps to Checkov CKV_GCP_* checks
- Covers IAM, logging, networking, VM, storage, and database controls
NIST SP 800-53 Mapping
| NIST Control | IaC Check | Checkov ID |
|---|---|---|
| AC-3 Access Enforcement | S3 bucket public access | CKV_AWS_18, CKV_AWS_20 |
| AU-2 Audit Events | CloudTrail enabled | CKV_AWS_35 |
| SC-8 Transmission Confidentiality | HTTPS/TLS enforcement | CKV_AWS_2 |
| SC-28 Protection at Rest | Encryption at rest | CKV_AWS_19, CKV_AWS_17 |
| SI-4 System Monitoring | CloudWatch/logging | CKV_AWS_24, CKV_AWS_66 |
OWASP SAMM - Secure Architecture
Security Architecture Level 2
- Validate infrastructure configurations against security standards before deployment
- Use automated tools to enforce architecture security requirements
Security Architecture Level 3
- Custom policies encode organization-specific architecture requirements
- Continuous validation prevents configuration drift from approved patterns
NIST SSDF (SP 800-218)
PO.1: Define Security Requirements
- IaC security policies translate security requirements into enforceable checks
- Custom policies capture organization-specific requirements
PW.5: Configure Software Securely
- PW.5.1: Configure software to have secure settings by default
- IaC scanning enforces secure defaults in infrastructure provisioning
workflows.md3.0 KB
Workflow Reference: IaC Security Scanning
IaC Scanning Pipeline
Terraform/IaC Code Change
│
▼
┌──────────────────┐
│ PR Created │
└──────┬───────────┘
│
├──────────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Checkov │ │ tfsec │
│ (2500+ rules)│ │ (Terraform) │
└──────┬───────┘ └──────┬───────┘
│ │
└──────────┬─────────┘
▼
┌──────────────────┐
│ SARIF Upload │
│ to GitHub │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Quality Gate │
│ (Block on HIGH+) │
└──────┬───────────┘
│
┌─────────┴──────────┐
▼ ▼
PASS FAIL
terraform apply Block merge
permitted + Fix requiredCheckov Command Reference
| Command | Purpose |
|---|---|
checkov -d ./terraform/ |
Scan directory |
checkov -f main.tf |
Scan single file |
checkov -f tfplan.json --framework terraform_plan |
Scan Terraform plan |
checkov --list |
List all available checks |
checkov -d . --check CKV_AWS_18 |
Run specific check |
checkov -d . --skip-check CKV_AWS_145 |
Skip specific check |
checkov -d . --bc-api-key KEY |
Upload to Bridgecrew |
checkov -d . --create-baseline |
Create baseline file |
checkov -d . --baseline BASELINE |
Scan against baseline |
checkov -d . --external-checks-dir ./custom/ |
Use custom checks |
checkov -d . --compact |
Compact output |
checkov -d . --output sarif |
SARIF format output |
Common Misconfigurations by Cloud Provider
AWS Top 10 IaC Misconfigurations
- S3 bucket public access enabled (CKV_AWS_18, CKV_AWS_20)
- Security group with open ingress 0.0.0.0/0 (CKV_AWS_23)
- RDS instance not encrypted (CKV_AWS_16)
- CloudTrail not enabled (CKV_AWS_35)
- EBS volume not encrypted (CKV_AWS_3)
- IAM policy with wildcard actions (CKV_AWS_1)
- ALB not using HTTPS (CKV_AWS_2)
- CloudWatch logs not encrypted (CKV_AWS_24)
- IMDSv2 not required (CKV_AWS_79)
- VPC flow logs not enabled (CKV_AWS_9)
Kubernetes Top Misconfigurations
- Container running as root (CKV_K8S_6)
- Privileged container (CKV_K8S_16)
- No resource limits (CKV_K8S_11, CKV_K8S_13)
- No readiness/liveness probes (CKV_K8S_9)
- hostNetwork enabled (CKV_K8S_19)
Scripts 2
agent.py6.8 KB
#!/usr/bin/env python3
"""Agent for scanning Infrastructure as Code templates for security misconfigurations."""
import json
import argparse
import subprocess
from datetime import datetime
from collections import Counter
from pathlib import Path
def run_checkov(target_path, framework=None):
"""Run Checkov IaC security scanner."""
cmd = ["checkov", "-d", target_path, "--output", "json", "--quiet"]
if framework:
cmd.extend(["--framework", framework])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
try:
return json.loads(result.stdout) if result.stdout.strip() else {"error": result.stderr}
except json.JSONDecodeError:
return {"raw": result.stdout[:2000], "error": result.stderr}
def run_tfsec(target_path):
"""Run tfsec Terraform security scanner."""
cmd = ["tfsec", target_path, "--format", "json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
try:
return json.loads(result.stdout) if result.stdout.strip() else {"error": result.stderr}
except json.JSONDecodeError:
return {"raw": result.stdout[:2000]}
def analyze_checkov_results(results):
"""Analyze Checkov scan results and categorize findings."""
if isinstance(results, list):
all_checks = results
elif isinstance(results, dict):
all_checks = results.get("results", {}).get("failed_checks", [])
else:
return {"error": "Invalid results format"}
by_severity = Counter()
by_resource_type = Counter()
by_check = Counter()
findings = []
for check in all_checks:
severity = check.get("severity", check.get("check_result", {}).get("severity", "MEDIUM"))
by_severity[severity] += 1
resource = check.get("resource", "unknown")
by_resource_type[resource.split(".")[0]] += 1
by_check[check.get("check_id", "unknown")] += 1
if severity in ("CRITICAL", "HIGH"):
findings.append({
"check_id": check.get("check_id", ""),
"check_name": check.get("check", check.get("name", "")),
"resource": resource,
"file": check.get("file_path", check.get("repo_file_path", "")),
"line": check.get("file_line_range", []),
"severity": severity,
"guideline": check.get("guideline", ""),
})
return {
"total_failures": len(all_checks),
"by_severity": dict(by_severity),
"by_resource_type": dict(by_resource_type.most_common(10)),
"top_checks": dict(by_check.most_common(10)),
"critical_findings": findings[:30],
}
def scan_terraform_files(dir_path):
"""Scan Terraform files for common security misconfigurations."""
findings = []
tf_files = list(Path(dir_path).rglob("*.tf"))
risky_patterns = {
'"0.0.0.0/0"': {"issue": "Open CIDR block", "severity": "HIGH"},
"cidr_blocks = [": {"issue": "Check CIDR block restrictions", "severity": "MEDIUM"},
"publicly_accessible": {"issue": "Public accessibility setting", "severity": "HIGH"},
"encrypted = false": {"issue": "Encryption disabled", "severity": "CRITICAL"},
"enable_logging = false": {"issue": "Logging disabled", "severity": "HIGH"},
"versioning {": {"issue": "Check versioning enabled", "severity": "MEDIUM"},
'protocol = "-1"': {"issue": "All protocols allowed", "severity": "HIGH"},
"from_port = 0": {"issue": "All ports open", "severity": "HIGH"},
}
for tf_file in tf_files:
try:
content = tf_file.read_text(encoding="utf-8", errors="ignore")
for pattern, info in risky_patterns.items():
if pattern in content:
lines = [i + 1 for i, line in enumerate(content.split("\n"))
if pattern in line]
for line in lines[:5]:
findings.append({
"file": str(tf_file),
"line": line,
"pattern": pattern,
"issue": info["issue"],
"severity": info["severity"],
})
except (OSError, PermissionError):
continue
return findings
def generate_ci_config(scanner="checkov", framework="terraform"):
"""Generate CI/CD pipeline config for IaC scanning."""
configs = {
"github_actions": f"""name: IaC Security Scan
on: [push, pull_request]
jobs:
iac-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run {scanner}
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: {framework}
soft_fail: false
output_format: sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
""",
"gitlab_ci": f"""iac-scan:
image: bridgecrew/checkov:latest
script:
- checkov -d . --framework {framework} --output junitxml > checkov.xml
artifacts:
reports:
junit: checkov.xml
""",
}
return configs
def main():
parser = argparse.ArgumentParser(description="IaC Security Scanning Agent")
parser.add_argument("--scan-dir", help="Directory to scan")
parser.add_argument("--framework", choices=["terraform", "cloudformation",
"kubernetes", "helm", "all"])
parser.add_argument("--scanner", choices=["checkov", "tfsec", "builtin"], default="builtin")
parser.add_argument("--gen-ci", action="store_true", help="Generate CI config")
parser.add_argument("--output", default="iac_security_report.json")
args = parser.parse_args()
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
if args.scan_dir:
if args.scanner == "checkov":
raw = run_checkov(args.scan_dir, args.framework)
analysis = analyze_checkov_results(raw)
report["results"]["checkov"] = analysis
print(f"[+] Checkov: {analysis.get('total_failures', 0)} failures")
elif args.scanner == "tfsec":
raw = run_tfsec(args.scan_dir)
report["results"]["tfsec"] = raw
print("[+] tfsec scan complete")
else:
findings = scan_terraform_files(args.scan_dir)
report["results"]["builtin_scan"] = findings
print(f"[+] Built-in scan: {len(findings)} findings")
if args.gen_ci:
configs = generate_ci_config(args.scanner or "checkov", args.framework or "terraform")
report["results"]["ci_configs"] = configs
print("[+] CI configs generated")
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()
process.py9.0 KB
#!/usr/bin/env python3
"""
IaC Security Scanning Pipeline Script
Runs Checkov and/or tfsec against IaC directories, aggregates findings,
evaluates quality gates, and generates reports.
Usage:
python process.py --iac-dir ./terraform --framework terraform
python process.py --iac-dir ./k8s --framework kubernetes --output report.json
"""
import argparse
import json
import os
import subprocess
import sys
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Optional
SEVERITY_MAP = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
@dataclass
class IaCFinding:
check_id: str
check_name: str
severity: str
resource: str
file_path: str
guideline: str
framework: str
tool: str
file_line_range: list = field(default_factory=list)
def run_checkov(iac_dir: str, framework: str = "terraform",
skip_checks: Optional[list] = None,
custom_checks_dir: Optional[str] = None,
baseline: Optional[str] = None) -> dict:
"""Run Checkov scan and return JSON results."""
cmd = [
"checkov", "-d", iac_dir,
"--framework", framework,
"--output", "json",
"--compact",
"--quiet"
]
if skip_checks:
cmd.extend(["--skip-check", ",".join(skip_checks)])
if custom_checks_dir:
cmd.extend(["--external-checks-dir", custom_checks_dir])
if baseline:
cmd.extend(["--baseline", baseline])
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if proc.stdout:
try:
return json.loads(proc.stdout)
except json.JSONDecodeError:
return {"error": "Failed to parse Checkov JSON output"}
return {"error": proc.stderr[:500] if proc.stderr else "No output from Checkov"}
except subprocess.TimeoutExpired:
return {"error": "Checkov scan timed out"}
except FileNotFoundError:
return {"error": "checkov not found. Install with: pip install checkov"}
def run_tfsec(iac_dir: str) -> dict:
"""Run tfsec scan and return JSON results."""
cmd = ["tfsec", iac_dir, "--format", "json", "--no-color"]
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if proc.stdout:
try:
return json.loads(proc.stdout)
except json.JSONDecodeError:
return {"error": "Failed to parse tfsec JSON output"}
return {"error": proc.stderr[:500] if proc.stderr else "No output from tfsec"}
except FileNotFoundError:
return {"error": "tfsec not found. Install from https://aquasecurity.github.io/tfsec/"}
def parse_checkov_results(checkov_json: dict) -> tuple:
"""Parse Checkov JSON output into findings."""
findings = []
passed_count = 0
skipped_count = 0
results_list = checkov_json if isinstance(checkov_json, list) else [checkov_json]
for result_block in results_list:
for check_type in result_block.get("results", {}):
if check_type == "passed_checks":
passed_count += len(result_block["results"].get("passed_checks", []))
elif check_type == "skipped_checks":
skipped_count += len(result_block["results"].get("skipped_checks", []))
elif check_type == "failed_checks":
for check in result_block["results"].get("failed_checks", []):
severity = check.get("severity", "MEDIUM")
if not severity or severity == "UNKNOWN":
severity = "MEDIUM"
findings.append(IaCFinding(
check_id=check.get("check_id", ""),
check_name=check.get("check_result", {}).get("name", check.get("name", "")),
severity=severity.upper(),
resource=check.get("resource", ""),
file_path=check.get("file_path", ""),
guideline=check.get("guideline", ""),
framework=check.get("check_type", "terraform"),
tool="checkov",
file_line_range=check.get("file_line_range", [])
))
return findings, passed_count, skipped_count
def parse_tfsec_results(tfsec_json: dict) -> list:
"""Parse tfsec JSON output into findings."""
findings = []
for result in tfsec_json.get("results", []):
findings.append(IaCFinding(
check_id=result.get("rule_id", result.get("long_id", "")),
check_name=result.get("rule_description", ""),
severity=result.get("severity", "MEDIUM").upper(),
resource=result.get("resource", ""),
file_path=result.get("location", {}).get("filename", ""),
guideline=result.get("resolution", ""),
framework="terraform",
tool="tfsec",
file_line_range=[
result.get("location", {}).get("start_line", 0),
result.get("location", {}).get("end_line", 0)
]
))
return findings
def evaluate_quality_gate(findings: list, threshold: str) -> dict:
"""Evaluate quality gate based on finding severities."""
threshold_level = SEVERITY_MAP.get(threshold.upper(), 1)
blocking = [f for f in findings if SEVERITY_MAP.get(f.severity, 3) <= threshold_level]
severity_counts = {}
for f in findings:
severity_counts[f.severity] = severity_counts.get(f.severity, 0) + 1
return {
"passed": len(blocking) == 0,
"threshold": threshold.upper(),
"total_findings": len(findings),
"blocking_count": len(blocking),
"severity_counts": severity_counts,
"blocking_details": [
{
"check_id": f.check_id,
"name": f.check_name,
"severity": f.severity,
"resource": f.resource,
"file": f.file_path,
"lines": f.file_line_range
}
for f in blocking[:25]
]
}
def main():
parser = argparse.ArgumentParser(description="IaC Security Scanning Pipeline")
parser.add_argument("--iac-dir", required=True, help="Directory containing IaC files")
parser.add_argument("--framework", default="terraform",
choices=["terraform", "cloudformation", "kubernetes", "helm", "all"])
parser.add_argument("--output", default="iac-security-report.json")
parser.add_argument("--severity-threshold", default="high",
choices=["critical", "high", "medium", "low"])
parser.add_argument("--fail-on-findings", action="store_true")
parser.add_argument("--skip-checks", nargs="*", help="Check IDs to skip")
parser.add_argument("--custom-checks-dir", default=None)
parser.add_argument("--run-tfsec", action="store_true", help="Also run tfsec")
args = parser.parse_args()
iac_dir = os.path.abspath(args.iac_dir)
all_findings = []
print(f"[*] Scanning IaC: {iac_dir} (framework: {args.framework})")
checkov_json = run_checkov(iac_dir, args.framework, args.skip_checks, args.custom_checks_dir)
if "error" in checkov_json:
print(f"[WARN] Checkov: {checkov_json['error']}")
else:
findings, passed, skipped = parse_checkov_results(checkov_json)
all_findings.extend(findings)
print(f" Checkov: {len(findings)} failed, {passed} passed, {skipped} skipped")
if args.run_tfsec and args.framework in ("terraform", "all"):
tfsec_json = run_tfsec(iac_dir)
if "error" not in tfsec_json:
tfsec_findings = parse_tfsec_results(tfsec_json)
all_findings.extend(tfsec_findings)
print(f" tfsec: {len(tfsec_findings)} findings")
quality_gate = evaluate_quality_gate(all_findings, args.severity_threshold)
report = {
"metadata": {
"directory": iac_dir,
"framework": args.framework,
"scan_date": datetime.now(timezone.utc).isoformat()
},
"quality_gate": quality_gate,
"findings": [
{
"check_id": f.check_id, "name": f.check_name,
"severity": f.severity, "resource": f.resource,
"file": f.file_path, "lines": f.file_line_range,
"guideline": f.guideline, "tool": f.tool
}
for f in sorted(all_findings, key=lambda x: SEVERITY_MAP.get(x.severity, 3))
]
}
output_path = os.path.abspath(args.output)
with open(output_path, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report: {output_path}")
if quality_gate["passed"]:
print(f"\n[PASS] Quality gate passed. {quality_gate['total_findings']} findings, none blocking.")
else:
print(f"\n[FAIL] {quality_gate['blocking_count']} blocking findings:")
for d in quality_gate["blocking_details"][:10]:
print(f" [{d['severity']}] {d['check_id']}: {d['resource']} ({d['file']})")
if args.fail_on_findings and not quality_gate["passed"]:
sys.exit(1)
if __name__ == "__main__":
main()