Files
gh-agentsecops-secopsagentkit/skills/devsecops/iac-checkov/references/suppression_guide.md
2025-11-29 17:51:02 +08:00

9.9 KiB

Checkov Suppression and Exception Handling Guide

Best practices for suppressing false positives and managing policy exceptions in Checkov.

Suppression Methods

Terraform

# Single check suppression with justification
resource "aws_s3_bucket" "public_site" {
  # checkov:skip=CKV_AWS_18:Public bucket for static website hosting
  bucket = "my-public-website"
  acl    = "public-read"
}

# Multiple checks suppression
resource "aws_security_group" "legacy" {
  # checkov:skip=CKV_AWS_23:Legacy app requires open access
  # checkov:skip=CKV_AWS_24:IPv6 not supported by application
  name = "legacy-sg"

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Kubernetes

# Annotation-based suppression
apiVersion: v1
kind: Pod
metadata:
  name: legacy-app
  annotations:
    checkov.io/skip: CKV_K8S_16=Legacy application requires elevated privileges
spec:
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      privileged: true

CloudFormation

Resources:
  PublicBucket:
    Type: AWS::S3::Bucket
    Metadata:
      checkov:
        skip:
          - id: CKV_AWS_18
            comment: "Public bucket for CDN origin"
    Properties:
      BucketName: my-public-bucket
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false

Configuration File Suppression

.checkov.yaml

# .checkov.yaml (project root)
skip-check:
  - CKV_AWS_8   # Ensure CloudWatch log groups encrypted
  - CKV_K8S_43  # Image pull policy Always

# Skip specific paths
skip-path:
  - .terraform/
  - node_modules/
  - vendor/

# Severity-based soft fail
soft-fail-on:
  - LOW
  - MEDIUM

# Hard fail on critical/high only
hard-fail-on:
  - CRITICAL
  - HIGH

CLI-Based Suppression

# Skip specific checks
checkov -d ./terraform --skip-check CKV_AWS_8,CKV_AWS_21

# Skip entire frameworks
checkov -d ./infra --skip-framework secrets

# Skip paths
checkov -d ./terraform --skip-path .terraform/ --skip-path vendor/

Suppression Governance

Approval Workflow

# .github/workflows/checkov-review.yml
name: Review Checkov Suppressions

on:
  pull_request:
    paths:
      - '**.tf'
      - '**.yaml'
      - '**.yml'

jobs:
  check-suppressions:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Check for New Suppressions
        run: |
          # Count suppressions in PR
          SUPPRESSIONS=$(git diff origin/main | grep -c "checkov:skip" || true)

          if [ "$SUPPRESSIONS" -gt 0 ]; then
            echo "::warning::PR contains $SUPPRESSIONS new suppression(s)"
            echo "Security team review required"
            # Request review from security team
          fi

Suppression Documentation Template

resource "aws_security_group" "example" {
  # checkov:skip=CKV_AWS_23:TICKET-1234 - Business justification here
  # Approved by: security-team@example.com
  # Review date: 2024-01-15
  # Expiration: 2024-06-15 (review quarterly)
  #
  # Compensating controls:
  # - WAF rule blocks malicious traffic
  # - Application-level authentication required
  # - IP allow-listing at load balancer
  # - 24/7 monitoring and alerting

  name = "approved-exception"
  # ... configuration
}

Suppression Best Practices

1. Always Provide Justification

# ❌ BAD: No justification
resource "aws_s3_bucket" "example" {
  # checkov:skip=CKV_AWS_18
  bucket = "my-bucket"
}

# ✅ GOOD: Clear business justification
resource "aws_s3_bucket" "example" {
  # checkov:skip=CKV_AWS_18:Public bucket required for static website hosting.
  # Content is non-sensitive marketing materials. CloudFront restricts direct access.
  bucket = "marketing-website"
}

2. Document Compensating Controls

resource "aws_security_group" "app" {
  # checkov:skip=CKV_AWS_23:Office IP range access required for developers
  #
  # Compensating controls:
  # 1. IP range limited to corporate /24 subnet (203.0.113.0/24)
  # 2. MFA required for VPN access to corporate network
  # 3. Additional application-level authentication
  # 4. Session timeout of 15 minutes
  # 5. All access logged to SIEM

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.0/24"]
  }
}

3. Set Expiration Dates

resource "aws_instance" "temp" {
  # checkov:skip=CKV_AWS_8:Temporary instance for POC
  # EXPIRES: 2024-03-31
  # After expiration: Remove or apply encryption

  ami           = "ami-12345678"
  instance_type = "t3.micro"
}

4. Use Granular Suppressions

# ❌ BAD: Suppress entire file or directory
# checkov:skip=* (Don't do this!)

# ✅ GOOD: Suppress specific checks on specific resources
resource "aws_s3_bucket" "example" {
  # checkov:skip=CKV_AWS_18:Specific reason for this resource only
  bucket = "specific-bucket"
}

Exception Categories

Legitimate Exceptions

1. Public Resources by Design

resource "aws_s3_bucket" "website" {
  # checkov:skip=CKV_AWS_18:Public bucket for static website
  # checkov:skip=CKV_AWS_93:Public access required by design
  # Content: Marketing materials (non-sensitive)
  # Access: Read-only via CloudFront

  bucket = "company-website"
}

2. Legacy System Constraints

apiVersion: v1
kind: Pod
metadata:
  name: legacy-app
  annotations:
    checkov.io/skip: CKV_K8S_16=Legacy app built before containers, requires host access
    # Migration plan: TICKET-5678
    # Target date: Q2 2024
spec:
  hostNetwork: true
  containers:
  - name: legacy
    image: legacy-app:1.0

3. Development/Testing Environments

resource "aws_db_instance" "dev_db" {
  # checkov:skip=CKV_AWS_17:Dev environment - backups not required
  # checkov:skip=CKV_AWS_61:Dev environment - encryption overhead not needed
  # Environment: Non-production only
  # Data: Synthetic test data (no PII/PHI)

  identifier = "dev-database"
  backup_retention_period = 0
  storage_encrypted = false

  tags = {
    Environment = "development"
  }
}

Temporary Exceptions

resource "aws_rds_cluster" "temp_unencrypted" {
  # checkov:skip=CKV_AWS_96:Temporary exception during migration
  # TICKET: INFRA-1234
  # EXPIRES: 2024-02-15
  # PLAN: Enable encryption at rest in Phase 2 migration
  # OWNER: platform-team@example.com

  cluster_identifier = "migration-temp"
  storage_encrypted = false
}

Suppression Anti-Patterns

Don't: Blanket Suppressions

# BAD: Suppress all checks
skip-check:
  - "*"

Don't: Suppress Without Documentation

# BAD: No explanation
resource "aws_s3_bucket" "example" {
  # checkov:skip=CKV_AWS_18
  bucket = "my-bucket"
}

Don't: Permanent Suppressions for Production

# BAD: Permanent suppression of critical security control
resource "aws_rds_cluster" "prod" {
  # checkov:skip=CKV_AWS_96:Too expensive
  # ^ This is unacceptable for production!

  cluster_identifier = "production-db"
  storage_encrypted = false
}

Don't: Suppress High/Critical Without Review

# DANGEROUS: Suppressing critical finding without security review
resource "aws_security_group" "prod" {
  # checkov:skip=CKV_AWS_23:Need access from anywhere
  # ^ Requires security team approval!

  ingress {
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Monitoring Suppressions

Track Suppression Metrics

# Count suppressions by type
grep -r "checkov:skip" ./terraform | \
  sed 's/.*checkov:skip=\([^:]*\).*/\1/' | \
  sort | uniq -c | sort -rn

# Find suppressions without justification
grep -r "checkov:skip=" ./terraform | \
  grep -v "checkov:skip=.*:.*"

Suppression Audit Report

#!/usr/bin/env python3
"""Generate suppression audit report."""

import re
import sys
from pathlib import Path
from datetime import datetime

def find_suppressions(directory):
    """Find all Checkov suppressions."""
    suppressions = []

    for file_path in Path(directory).rglob('*.tf'):
        with open(file_path) as f:
            content = f.read()

        # Find suppressions
        matches = re.findall(
            r'#\s*checkov:skip=([^:]+):(.*)',
            content
        )

        for check_id, reason in matches:
            suppressions.append({
                'file': str(file_path),
                'check_id': check_id.strip(),
                'reason': reason.strip()
            })

    return suppressions

def generate_report(suppressions):
    """Generate markdown report."""
    print("# Checkov Suppression Audit Report")
    print(f"\nGenerated: {datetime.now().isoformat()}")
    print(f"\nTotal Suppressions: {len(suppressions)}\n")

    print("## Suppressions by Check")
    check_counts = {}
    for s in suppressions:
        check_counts[s['check_id']] = check_counts.get(s['check_id'], 0) + 1

    for check_id, count in sorted(check_counts.items(), key=lambda x: -x[1]):
        print(f"- {check_id}: {count}")

    print("\n## All Suppressions")
    for s in suppressions:
        print(f"\n### {s['file']}")
        print(f"**Check:** {s['check_id']}")
        print(f"**Reason:** {s['reason'] or '(no justification provided)'}")

if __name__ == '__main__':
    directory = sys.argv[1] if len(sys.argv) > 1 else './terraform'
    suppressions = find_suppressions(directory)
    generate_report(suppressions)

Quarterly Review Process

  1. Generate Suppression Report: List all active suppressions
  2. Review Expirations: Check for expired temporary suppressions
  3. Validate Justifications: Ensure reasons still apply
  4. Verify Compensating Controls: Confirm controls are still in place
  5. Update or Remove: Update suppressions or fix underlying issues

Additional Resources