Initial commit
This commit is contained in:
431
skills/devsecops/iac-checkov/references/suppression_guide.md
Normal file
431
skills/devsecops/iac-checkov/references/suppression_guide.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# Checkov Suppression and Exception Handling Guide
|
||||
|
||||
Best practices for suppressing false positives and managing policy exceptions in Checkov.
|
||||
|
||||
## Suppression Methods
|
||||
|
||||
### Inline Suppression (Recommended)
|
||||
|
||||
#### Terraform
|
||||
|
||||
```hcl
|
||||
# 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
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```yaml
|
||||
# .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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```hcl
|
||||
# ❌ 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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```hcl
|
||||
# ❌ 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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```hcl
|
||||
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
|
||||
|
||||
```yaml
|
||||
# BAD: Suppress all checks
|
||||
skip-check:
|
||||
- "*"
|
||||
```
|
||||
|
||||
### ❌ Don't: Suppress Without Documentation
|
||||
|
||||
```hcl
|
||||
# BAD: No explanation
|
||||
resource "aws_s3_bucket" "example" {
|
||||
# checkov:skip=CKV_AWS_18
|
||||
bucket = "my-bucket"
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Don't: Permanent Suppressions for Production
|
||||
|
||||
```hcl
|
||||
# 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
|
||||
|
||||
```hcl
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```python
|
||||
#!/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
|
||||
|
||||
- [Checkov Suppression Documentation](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html)
|
||||
- [Security Exception Management Best Practices](https://owasp.org/www-community/Security_Exception_Management)
|
||||
Reference in New Issue
Block a user