Infrastructure as Code Security (IaC Security)
IaC Security bezeichnet die Sicherheitspraktiken für Infrastructure as Code - Terraform, Pulumi, AWS CloudFormation, Bicep und Ansible. Sicherheitsfehler in IaC-Templates führen direkt zu unsicherer Cloud-Infrastruktur: öffentliche S3-Buckets, überprivilegierte IAM-Rollen, fehlende Verschlüsselung, exponierte Ports. IaC-Scanning-Tools wie Checkov, tfsec und Trivy erkennen Fehlkonfigurationen vor dem Deployment.
IaC Security schließt eine kritische Lücke: Wenn Infrastruktur als Code definiert wird, können Sicherheitsprobleme bereits im Code gefunden werden - bevor die unsichere Ressource überhaupt existiert. Ein Terraform-Template mit acl = "public-read" für einen S3-Bucket kann in der CI/CD-Pipeline erkannt und abgeleht werden, bevor ein Entwickler auf Apply drückt.
Typische IaC-Sicherheitsfehler
Häufigste Fehlkonfigurationen in Terraform (AWS):
1. Öffentlicher S3-Bucket:
# FALSCH:
resource "aws_s3_bucket" "data" {
bucket = "firma-kundendaten"
acl = "public-read" # KRITISCH: Alle können Daten lesen!
}
# RICHTIG:
resource "aws_s3_bucket" "data" {
bucket = "firma-kundendaten"
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
2. Überprivilegierte IAM-Rolle:
# FALSCH:
resource "aws_iam_policy" "app_policy" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = "*" # Alle Aktionen!
Resource = "*" # Alle Ressourcen!
}]
})
}
# RICHTIG (Least Privilege):
resource "aws_iam_policy" "app_policy" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = ["arn:aws:s3:::firma-app-data/*"]
}]
})
}
3. Fehlende Verschlüsselung (RDS):
# FALSCH:
resource "aws_db_instance" "prod" {
storage_encrypted = false # Oder weggelassen (Default: false!)
}
# RICHTIG:
resource "aws_db_instance" "prod" {
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
}
4. Security Group zu offen:
# FALSCH:
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # SSH aus dem Internet!
}
# RICHTIG:
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion.id # Nur Bastion!
}
5. Fehlende VPC Flow Logs:
# FALSCH: VPC ohne Flow Logs → kein Netzwerk-Audit-Trail
# RICHTIG:
resource "aws_flow_log" "vpc" {
iam_role_arn = aws_iam_role.flow_logs.arn
log_destination = aws_cloudwatch_log_group.vpc_logs.arn
traffic_type = "ALL"
vpc_id = aws_vpc.main.id
}
IaC-Scanning-Tools
Checkov (Bridgecrew / Palo Alto):
→ Open Source, umfangreichste Regelsammlung
→ Unterstützt: Terraform, CloudFormation, Kubernetes, Dockerfile, Bicep, ARM
→ 2500+ integrierte Checks
→ SARIF-Output für GitHub Security Tab
Installation:
pip install checkov
Terraform-Scan:
checkov -d ./terraform --framework terraform
# Oder spezifische Checks:
checkov -d ./terraform --check CKV_AWS_18,CKV_AWS_21
CI/CD Integration (GitHub Actions):
- name: Checkov IaC Scan
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: results.sarif
soft_fail: false # Pipeline schlägt fehl bei CRITICAL!
Beispiel-Output:
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.data
File: /terraform/s3.tf:1-5
Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
PASSED for resource: aws_s3_bucket.backup
tfsec (Aqua Security):
→ Terraform-spezialisiert, sehr schnell
→ SARIF + JUnit Output
→ Custom Checks in Rego (OPA)
Scan:
tfsec ./terraform
tfsec ./terraform --format sarif --out tfsec.sarif
tfsec ./terraform --minimum-severity HIGH
GitHub Actions:
- name: tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform/
github_token: ${{ secrets.GITHUB_TOKEN }}
Trivy (Aqua Security - umfassend):
→ IaC + Container + SCA + Secrets
→ Ein Tool für alle Scan-Arten
Terraform scannen:
trivy config ./terraform
trivy config --severity HIGH,CRITICAL ./terraform
trivy config --format sarif --output trivy.sarif ./terraform
Kubernetes Manifests:
trivy config ./k8s/
# Prüft: privileged containers, root user, capabilities, etc.
Dockerfile:
trivy config ./Dockerfile
KICS (Checkmarx - Open Source):
→ Unterstützt 24+ IaC-Sprachen
→ Sehr breite Abdeckung
kics scan -p ./infrastructure -o results.json
Secrets in IaC
KRITISCH: Secrets nie in IaC-Code!
Häufig versehentlich eingecheckt:
# NIEMALS:
resource "aws_db_instance" "prod" {
password = "SuperSecret123!" # Im Git-Repository!
}
resource "aws_secretsmanager_secret_version" "db" {
secret_string = jsonencode({
password = "MyPassword" # Im State File!
})
}
Terraform State File:
→ Alle Ressourcen-Attribute gespeichert - inklusive Passwörter!
→ terraform.tfstate ist HOCHSENSIBEL
→ Lokal: .gitignore für *.tfstate und *.tfstate.backup
→ Remote Backend: S3 + DynamoDB (mit Verschlüsselung)
# Backend mit Verschlüsselung:
terraform {
backend "s3" {
bucket = "firma-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-central-1"
encrypt = true # KMS-Verschlüsselung!
kms_key_id = "arn:aws:kms:..."
dynamodb_table = "terraform-state-lock"
}
}
Richtige Secrets-Verwaltung in IaC:
Option 1 - Referenz auf Secrets Manager:
# Terraform: AWS Secrets Manager referenzieren (nicht Wert hardcoden!)
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password" # Name, kein Wert!
}
resource "aws_db_instance" "prod" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
# Problem: Passwort landet trotzdem im State File!
Option 2 - Leere Ressource, Passwort außerhalb setzen:
resource "aws_db_instance" "prod" {
# password NICHT in Terraform - per API oder Konsole setzen
lifecycle {
ignore_changes = [password] # Terraform überschreibt nicht
}
}
Option 3 - External Data Source + Vault:
data "external" "vault_secret" {
program = ["vault", "read", "-format=json", "secret/db/password"]
}
# Vault liefert Secret zur Laufzeit - nie im Code!
Secret-Scanning für IaC:
# TruffleHog:
trufflehog git file://./ # Scannt Git-History auf Secrets!
# TruffleHog findet auch alte Commits!
# detect-secrets (Yelp):
detect-secrets scan --all-files > .secrets.baseline
detect-secrets audit .secrets.baseline
IaC-Sicherheit in CI/CD
Vollständige DevSecOps-Pipeline für Terraform:
name: Terraform Security Pipeline
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Für git-history Secret-Scan
# 1. Secret Scanning (vor allem anderen!)
- name: TruffleHog Secret Scan
uses: trufflesecurity/trufflehog@main
with:
path: ./
extra_args: --only-verified
# 2. IaC Linting + Formatting
- name: Terraform Format Check
run: terraform fmt -check -recursive ./terraform
- name: Terraform Validate
run: |
cd terraform
terraform init -backend=false
terraform validate
# 3. IaC Security Scanning
- name: Checkov Scan
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
output_format: sarif
output_file_path: checkov.sarif
soft_fail: true # Erstmal nur Warning
- name: tfsec Scan
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform/
# 4. SARIF Upload für GitHub Security Tab
- name: Upload SARIF Results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: checkov.sarif
# 5. Plan nur nach Security-Checks (abhängig!)
plan:
needs: security
runs-on: ubuntu-latest
steps:
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Checkov auf Plan:
run: checkov -f tfplan --framework terraform_plan
Checkov Custom Policy (eigene Regeln):
# custom_checks/S3_bucket_tags.py
from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class S3BucketRequiredTags(BaseResourceCheck):
def __init__(self):
name = "S3 Bucket muss Owner und CostCenter Tags haben"
id = "FIRMA_S3_001"
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):
tags = conf.get("tags", [{}])[0]
if isinstance(tags, dict):
if "Owner" in tags and "CostCenter" in tags:
return CheckResult.PASSED
return CheckResult.FAILED
scanner = S3BucketRequiredTags()
# Ausführen:
checkov -d ./terraform --external-checks-dir ./custom_checks