devsecops

Implementing Infrastructure as Code Security Scanning

This skill covers implementing automated security scanning for Infrastructure as Code (IaC) templates using tools like Checkov, tfsec, and KICS. It addresses detecting misconfigurations in Terraform, CloudFormation, Kubernetes manifests, and Helm charts before deployment, establishing policy-based governance, and integrating IaC scanning into CI/CD pipelines to prevent insecure cloud resource provisioning.

checkovcicddevsecopsiac-securitysecure-sdlcterraformtfsec
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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_6

Step 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: tfsec

Step 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: false

Step 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,sarif

Key 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:

  1. Enable Checkov in the CI/CD pipeline for all Terraform changes
  2. Enforce CKV_AWS_18 (no public read ACL), CKV_AWS_19 (encryption), CKV_AWS_20 (no public access block disabled)
  3. Create a custom policy requiring the aws_s3_bucket_public_access_block resource for every S3 bucket
  4. Set soft_fail: false to block PR merges when S3 security checks fail
  5. 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)
Source materials

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_21

tfsec CLI

# Scan directory
tfsec /path/to/tf --format json
# Exclude specific rules
tfsec . --exclude aws-s3-enable-bucket-logging
# Minimum severity
tfsec . --minimum-severity HIGH

Common 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: false

References

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 required

Checkov 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

  1. S3 bucket public access enabled (CKV_AWS_18, CKV_AWS_20)
  2. Security group with open ingress 0.0.0.0/0 (CKV_AWS_23)
  3. RDS instance not encrypted (CKV_AWS_16)
  4. CloudTrail not enabled (CKV_AWS_35)
  5. EBS volume not encrypted (CKV_AWS_3)
  6. IAM policy with wildcard actions (CKV_AWS_1)
  7. ALB not using HTTPS (CKV_AWS_2)
  8. CloudWatch logs not encrypted (CKV_AWS_24)
  9. IMDSv2 not required (CKV_AWS_79)
  10. VPC flow logs not enabled (CKV_AWS_9)

Kubernetes Top Misconfigurations

  1. Container running as root (CKV_K8S_6)
  2. Privileged container (CKV_K8S_16)
  3. No resource limits (CKV_K8S_11, CKV_K8S_13)
  4. No readiness/liveness probes (CKV_K8S_9)
  5. hostNetwork enabled (CKV_K8S_19)

Scripts 2

agent.py6.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 2.0 KB
Keep exploring