<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->

# Terraform & IaC Security Reference

## Secure IAM Policy (Least Privilege)

```hcl
resource "aws_iam_policy" "s3_read_only" {
  name   = "s3-read-only-${var.env}"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:GetObject", "s3:ListBucket"]
      Resource = [
        "arn:aws:s3:::${var.bucket_name}",
        "arn:aws:s3:::${var.bucket_name}/*"
      ]
      Condition = {
        StringEquals = { "aws:RequestedRegion" = var.region }
      }
    }]
  })
}
```

## S3 Bucket with Encryption and Blocking

```hcl
resource "aws_s3_bucket" "secure" {
  bucket = var.bucket_name
}
resource "aws_s3_bucket_server_side_encryption_configuration" "secure" {
  bucket = aws_s3_bucket.secure.id
  rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" } }
}
resource "aws_s3_bucket_public_access_block" "secure" {
  bucket                  = aws_s3_bucket.secure.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

## Security Group — Deny 0.0.0.0/0 Ingress

```hcl
resource "aws_security_group_rule" "web_ingress" {
  type        = "ingress"
  from_port   = 443
  to_port     = 443
  protocol    = "tcp"
  cidr_blocks = [var.allowed_cidr]  # never "0.0.0.0/0" for admin ports
  security_group_id = aws_security_group.web.id
}
```

## KMS Key with Rotation

```hcl
resource "aws_kms_key" "main" {
  description             = "Application encryption key"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  key_usage               = "ENCRYPT_DECRYPT"
  policy = jsonencode({ ... })  # deny * principal, explicit allow list
}
```

## Static Analysis

```bash
tfsec . --format json | jq '.results[] | select(.severity == "HIGH")'
checkov -d . --framework terraform --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_57
# CKV_AWS_18: S3 logging  CKV_AWS_19: S3 SSE  CKV_AWS_57: no public S3
```

## Terragrunt DRY Pattern

```
infra/
├── terragrunt.hcl          # root config, remote state backend
├── prod/
│   └── vpc/terragrunt.hcl  # include root + prod-specific vars
└── staging/
    └── vpc/terragrunt.hcl
```

```hcl
remote_state {
  backend = "s3"
  config  = { bucket = "tfstate-${local.account_id}", key = "${path_relative_to_include()}/terraform.tfstate", encrypt = true }
}
```

## Anti-Patterns

- Hardcoding AWS creds in provider block — use `aws_profile` or IAM role.
- `lifecycle { prevent_destroy = false }` on KMS keys or S3 buckets — accidental destroy = data loss.
