Terraform-Sicherheitsanalyzer

Analysieren Sie Terraform-HCL-Code auf Sicherheitsfehlkonfigurationen und offene Ports

Terraform Security Analysis

Terraform security analysis is the practice of scanning Infrastructure-as-Code (IaC) configurations to identify insecure resource definitions, overly permissive access policies, unencrypted storage, exposed network surfaces, and compliance violations before infrastructure is provisioned. Terraform configurations declaratively define cloud resources — from S3 buckets and RDS instances to IAM roles and security groups — but a syntactically valid configuration is not necessarily a secure one. A publicly accessible S3 bucket, a security group allowing ingress from 0.0.0.0/0 on all ports, or an RDS instance without encryption at rest are all valid Terraform that provisions dangerous infrastructure. Security analysis catches these misconfigurations at the code review stage, long before terraform apply creates real attack surfaces in production.

The shift-left security approach treats infrastructure code with the same rigor as application code. Just as static analysis tools detect SQL injection or buffer overflows in application source, Terraform security analyzers detect overly permissive IAM policies, missing encryption configurations, disabled logging, and network rules that violate the principle of least privilege. By integrating these checks into CI/CD pipelines and developer workflows, teams catch security issues when the cost of remediation is a code change rather than an incident response — reducing mean time to remediation from days to minutes and preventing security debt from accumulating in deployed infrastructure.

Common Terraform Security Misconfigurations

Certain categories of misconfiguration appear repeatedly across Terraform codebases, regardless of cloud provider or team size. Understanding these patterns is the first step toward preventing them:

  • Public S3 buckets and storage: Setting acl = "public-read" or omitting block_public_access configuration on aws_s3_bucket resources exposes bucket contents to the internet. Numerous data breaches trace back to this single misconfiguration — leaked customer records, backup databases, and credential files discovered by automated scanners within hours of exposure.
  • Overly permissive security groups: Ingress rules with cidr_blocks = ["0.0.0.0/0"] on sensitive ports (SSH 22, RDP 3389, database ports 3306/5432/27017) allow connection attempts from any IP address globally. Brute-force attacks target these open ports continuously, and a single weak password becomes a complete compromise.
  • Unencrypted data stores: RDS instances without storage_encrypted = true, EBS volumes without encrypted = true, and ElastiCache clusters without at_rest_encryption_enabled store sensitive data in plaintext on disk. Physical access to storage media, snapshot theft, or cross-account access bypasses all application-layer security controls.
  • Wildcard IAM policies: Using "Action": "*" or "Resource": "*" in IAM policy documents grants unrestricted permissions. An compromised credential with AdministratorAccess can delete all resources, exfiltrate all data, and provision cryptocurrency miners — the maximum possible blast radius from a single leaked key.
  • Disabled logging and monitoring: Resources without CloudTrail enabled, VPC Flow Logs configured, or access logging on load balancers create blind spots where attacks proceed undetected. Post-incident forensics becomes impossible without audit trails.
  • Secrets in Terraform state: Storing passwords, API keys, or certificates directly in terraform.tfvars or as default variable values embeds them in state files. Terraform state contains every attribute of every managed resource in plaintext — a state file is a complete inventory of credentials for the entire infrastructure.

Infrastructure Security Patterns and Best Practices

Secure Terraform configurations follow established patterns that enforce defense in depth, least privilege, and encryption by default:

  • Least privilege IAM: Define granular IAM policies that specify exact actions on exact resources. Instead of "s3:*" on "*", use "s3:GetObject" on "arn:aws:s3:::my-bucket/prefix/*". Use aws_iam_policy_document data sources with conditions for additional constraints like source IP, MFA requirement, or time-based access.
  • Encryption everywhere: Enable encryption at rest for all storage resources (S3, RDS, EBS, DynamoDB, ElastiCache) and encryption in transit via TLS for all network communication. Use AWS KMS customer-managed keys (CMK) rather than AWS-managed keys for granular key rotation control and access auditing.
  • Network segmentation: Place resources in private subnets with no direct internet access. Route outbound traffic through NAT gateways. Restrict security group rules to specific CIDR ranges for known office IPs or VPN endpoints. Use VPC endpoints for AWS service access without traversing the public internet.
  • Immutable infrastructure: Use launch templates with hardened AMIs rather than provisioning instances that require post-deployment configuration. Treat infrastructure as disposable — replace rather than patch, rebuild rather than repair.
  • State file protection: Store Terraform state in encrypted S3 backends with versioning enabled, DynamoDB locking, and restrictive bucket policies. Never commit state files to version control. Use sensitive = true on variables and outputs containing credentials to prevent them from appearing in CLI output.

Compliance Frameworks and CIS Benchmarks

Industry compliance frameworks provide structured rule sets that security analyzers evaluate Terraform configurations against. These benchmarks codify security best practices into auditable, enforceable policies:

  • CIS AWS Foundations Benchmark: Covers 49+ controls across IAM, logging, monitoring, networking, and storage. Key checks include ensuring MFA is enabled for the root account, CloudTrail is enabled in all regions, S3 bucket logging is active, and security groups do not allow unrestricted ingress to high-risk ports. Each control maps directly to Terraform resource attributes that can be validated statically.
  • CIS Azure Foundations Benchmark: Defines controls for Azure-specific resources including Network Security Groups, Storage Account configurations, Azure SQL auditing, and Key Vault access policies. Validates that azurerm_storage_account resources have min_tls_version = "TLS1_2" and enable_https_traffic_only = true.
  • CIS GCP Foundations Benchmark: Covers Compute Engine firewall rules, Cloud Storage bucket IAM, BigQuery dataset access, and Cloud SQL instance configurations. Ensures that google_compute_firewall rules do not allow SSH from 0.0.0.0/0 and that Cloud SQL instances require SSL connections.
  • SOC 2 and HIPAA mapping: Terraform security rules can be mapped to SOC 2 trust service criteria (security, availability, confidentiality) and HIPAA technical safeguards. Encryption at rest satisfies HIPAA §164.312(a)(2)(iv), while access logging maps to SOC 2 CC6.1 monitoring requirements.

A security analyzer evaluates each resource block against applicable benchmark rules, reporting violations with severity levels (critical, high, medium, low) and direct references to the specific CIS control or compliance requirement that the finding violates.

Security Group and Network ACL Analysis

Network security configuration is one of the highest-impact areas for Terraform security analysis. A single overly permissive rule can expose entire application stacks to the internet:

  • Port exposure analysis: Evaluates which ports are open to which CIDR ranges. Flags administrative ports (SSH 22, RDP 3389, WinRM 5985) open to 0.0.0.0/0, database ports (MySQL 3306, PostgreSQL 5432, MongoDB 27017, Redis 6379) accessible from outside the VPC, and any rule using protocol = "-1" (all traffic) without CIDR restriction.
  • Egress rule validation: Default egress rules allowing all outbound traffic (0.0.0.0/0 on all ports) enable data exfiltration and command-and-control communication. Hardened configurations restrict egress to known required destinations — specific CIDR ranges for API endpoints, port 443 for HTTPS services, and VPC endpoint prefixes for AWS service access.
  • Cross-reference with resource types: A security group attached to a public-facing ALB has different risk characteristics than one on a private backend service. The analyzer considers the security group's attachment context — a rule allowing port 80/443 from 0.0.0.0/0 is expected on a load balancer but critical on a database instance.
  • NACL layer analysis: Network ACLs provide stateless subnet-level filtering as a second defense layer. The analyzer verifies that NACLs do not accidentally deny legitimate traffic (blocking ephemeral ports breaks TCP return traffic) and that they complement security group rules rather than creating conflicting allow/deny logic.

Code Examples

Insecure vs Secure Terraform Configuration

# ❌ INSECURE — Common misconfigurations found by security analysis

resource "aws_s3_bucket" "data" {
  bucket = "company-sensitive-data"
  # Missing: block_public_access, versioning, encryption, logging
}

resource "aws_s3_bucket_acl" "data" {
  bucket = aws_s3_bucket.data.id
  acl    = "public-read" # ❌ CRITICAL: Publicly accessible bucket
}

resource "aws_security_group" "web" {
  name = "web-server-sg"

  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # ❌ CRITICAL: All ports open to internet
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"] # ⚠️ HIGH: Unrestricted egress
  }
}

resource "aws_db_instance" "main" {
  engine         = "postgres"
  instance_class = "db.t3.medium"
  # ❌ storage_encrypted    = false (default)
  # ❌ publicly_accessible  = true
  # ❌ No backup or logging
  publicly_accessible = true
  skip_final_snapshot = true
}

resource "aws_iam_policy" "admin" {
  name = "developer-policy"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "*"        # ❌ CRITICAL: Wildcard actions
      Resource = "*"        # ❌ CRITICAL: Wildcard resources
    }]
  })
}

# ─────────────────────────────────────────────────

# ✅ SECURE — Hardened configuration passing all CIS checks

resource "aws_s3_bucket" "data" {
  bucket = "company-sensitive-data"
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id
  versioning_configuration {
    status = "Enabled" # ✅ Version history for recovery
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"     # ✅ KMS encryption at rest
      kms_master_key_id = aws_kms_key.data.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "data" {
  bucket                  = aws_s3_bucket.data.id
  block_public_acls       = true  # ✅ Block public ACLs
  block_public_policy     = true  # ✅ Block public policies
  ignore_public_acls      = true  # ✅ Ignore existing public ACLs
  restrict_public_buckets = true  # ✅ Restrict public access
}

resource "aws_security_group" "web" {
  name   = "web-server-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"] # ✅ VPC-only HTTPS access
  }

  ingress {
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    security_groups = [aws_security_group.bastion.id] # ✅ SSH via bastion only
  }

  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # ✅ HTTPS-only egress
  }
}

resource "aws_db_instance" "main" {
  engine               = "postgres"
  instance_class       = "db.t3.medium"
  storage_encrypted    = true                    # ✅ Encryption at rest
  kms_key_id           = aws_kms_key.rds.arn
  publicly_accessible  = false                   # ✅ Private subnet only
  deletion_protection  = true                    # ✅ Prevent accidental deletion
  backup_retention_period = 7                    # ✅ 7-day backup retention
  db_subnet_group_name = aws_db_subnet_group.private.name

  enabled_cloudwatch_logs_exports = [
    "postgresql", "upgrade"                      # ✅ Audit logging enabled
  ]
}

resource "aws_iam_policy" "developer" {
  name = "developer-policy"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:GetObject", "s3:ListBucket"] # ✅ Specific actions
        Resource = [
          aws_s3_bucket.data.arn,                     # ✅ Specific bucket
          "${aws_s3_bucket.data.arn}/*"
        ]
      },
      {
        Effect = "Allow"
        Action = ["logs:GetLogEvents", "logs:FilterLogEvents"]
        Resource = "arn:aws:logs:*:*:log-group:/app/*" # ✅ Scoped resource
      }
    ]
  })
}

Standards & Specifications