Implementing IaC Security Scanning
Implementing IaC Security Scanning
Static analysis of IaC templates identifies misconfigurations before deployment. Unlike application code scanning, IaC scanning must understand cloud provider APIs, resource relationships, and security best practices. Modern IaC scanners combine rule-based checking with cloud provider knowledge to identify both obvious misconfigurations and subtle security issues.
# Example Terraform with security considerations
# main.tf - Secure AWS infrastructure
# KMS key for encryption
resource "aws_kms_key" "main" {
description = "KMS key for ${var.environment} environment"
deletion_window_in_days = 30
enable_key_rotation = true
tags = merge(
var.common_tags,
{
Name = "${var.project}-${var.environment}-kms"
Security = "encryption-at-rest"
}
)
}
# S3 bucket with security best practices
resource "aws_s3_bucket" "secure_bucket" {
bucket = "${var.project}-${var.environment}-data"
tags = merge(
var.common_tags,
{
Name = "${var.project}-${var.environment}-data"
DataClassification = var.data_classification
}
)
}
# Block all public access
resource "aws_s3_bucket_public_access_block" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning for data protection
resource "aws_s3_bucket_versioning" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
versioning_configuration {
status = "Enabled"
}
}
# Enable encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.main.arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true
}
}
# Enable logging
resource "aws_s3_bucket_logging" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "s3-access-logs/"
}
# Lifecycle policies for compliance
resource "aws_s3_bucket_lifecycle_configuration" "secure_bucket" {
bucket = aws_s3_bucket.secure_bucket.id
rule {
id = "compliance-retention"
status = "Enabled"
transition {
days = 90
storage_class = "STANDARD_IA"
}
transition {
days = 180
storage_class = "GLACIER"
}
expiration {
days = var.retention_days
}
}
}
# RDS instance with security hardening
resource "aws_db_instance" "secure_database" {
identifier = "${var.project}-${var.environment}-db"
# Security configurations
storage_encrypted = true
kms_key_id = aws_kms_key.main.arn
backup_retention_period = 30
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
deletion_protection = var.environment == "production" ? true : false
enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]
# Network security
db_subnet_group_name = aws_db_subnet_group.private.name
vpc_security_group_ids = [aws_security_group.database.id]
publicly_accessible = false
# Authentication
iam_database_authentication_enabled = true
# Monitoring
performance_insights_enabled = true
performance_insights_retention_period = 7
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
tags = merge(
var.common_tags,
{
Name = "${var.project}-${var.environment}-db"
Backup = "required"
}
)
}
# Security group with least privilege
resource "aws_security_group" "database" {
name_prefix = "${var.project}-${var.environment}-db-"
description = "Security group for RDS database"
vpc_id = aws_vpc.main.id
ingress {
description = "MySQL/Aurora from application"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.application.id]
}
egress {
description = "Deny all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["127.0.0.1/32"]
}
lifecycle {
create_before_destroy = true
}
tags = merge(
var.common_tags,
{
Name = "${var.project}-${var.environment}-db-sg"
}
)
}
Pipeline integration of IaC scanning requires balancing security thoroughness with deployment speed. Pre-commit hooks can catch basic issues before code commit. Pull request scanning provides comprehensive analysis with detailed feedback. Pre-deployment scanning offers final validation before resource creation. Each stage uses different scanning depths optimized for feedback speed.
# GitHub Actions workflow for IaC security
name: IaC Security Pipeline
on:
pull_request:
paths:
- 'terraform/**'
- 'cloudformation/**'
- 'kubernetes/**'
jobs:
terraform-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: TFSec Security Scan
uses: aquasecurity/[email protected]
with:
working_directory: ./terraform
format: json
out: tfsec-results.json
- name: Checkov Policy Scan
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
output_format: json
output_file_path: ./checkov-results.json
- name: Terrascan
run: |
docker run --rm -v $(pwd):/src accurics/terrascan scan \
-i terraform -d /src/terraform \
-o json > terrascan-results.json
- name: Open Policy Agent Validation
run: |
# Download OPA binary
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
chmod +x opa
# Validate against policies
./opa eval -d policies/ -i terraform/main.tf.json \
"data.terraform.deny[_]" > opa-results.json
- name: Aggregate Results
run: |
python3 scripts/aggregate_iac_scan_results.py \
--tfsec tfsec-results.json \
--checkov checkov-results.json \
--terrascan terrascan-results.json \
--opa opa-results.json \
--output security-report.md
- name: Comment PR
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('security-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
- name: Fail on Critical Issues
run: |
if grep -q "CRITICAL" security-report.md; then
echo "Critical security issues found!"
exit 1
fi