WAF++ WAF++
Back to WAF++ Homepage

Best Practice: Encryption Strategy

Context

Encryption is the last line of defense: even if access control, network, and all other layers fail, encryption protects the data.

Common encryption mistakes:

  • S3 buckets with SSE-S3 (AES256) instead of CMK – AWS manages the key, no real key ownership

  • EBS volumes without encryption-by-default configuration

  • Load balancers that still accept TLS 1.0 and 1.1

  • Self-signed certificates in production without rotation

  • No distinction between data categories (PII vs. operational)

Encryption Strategy: Which Data Requires CMK?

Data Category Minimum Requirement Recommendation Examples

PII (personal data)

CMK

CMK with key policy

Customer data, user profiles, email addresses

Financial data

CMK

CMK or BYOK

Transactions, account data, payment information

Health data

CMK

CMK with HSM backing

Patient data, diagnostic information

Internal audit logs

CMK

CMK

CloudTrail, application logs

Operational data

SSE-KMS (CMK recommended)

CMK

Metrics, monitoring data

Public assets

SSE-S3 acceptable

SSE-S3

Static website assets, public documents

KMS Key Hierarchy

KMS Key Hierarchy:

Master Keys (CMK) – one per data category:
├── cmk-pii           → RDS customer database, S3 PII bucket
├── cmk-financial     → RDS financial data, DynamoDB transactions
├── cmk-logs          → CloudTrail S3, CloudWatch log groups
├── cmk-secrets       → Secrets Manager keys
├── cmk-backup        → AWS Backup vault
└── cmk-general       → All other production resources

Envelope Encryption (automatically by AWS):
  CMK (KMS)
  └── encrypts: Data Encryption Key (DEK, 256-bit AES)
      └── DEK encrypts: actual data in S3/RDS/EBS

Envelope Encryption Explained

Envelope encryption is the concept where a data encryption key (DEK) is encrypted with another key (CMK):

  1. Application requests DEK from KMS

  2. KMS generates DEK and an encrypted copy (DEK encrypted with CMK)

  3. Application encrypts data with DEK

  4. Encrypted DEK is stored together with the data

  5. CMK never leaves KMS – only DEKs are handed to applications

Advantage: When the CMK rotates, only DEKs are re-encrypted – not all data. When the CMK is deleted, all data protected by it immediately becomes undecryptable (cryptographic erasure).

Technical Implementation

KMS CMK with Correct Key Policy

# CMK for PII data
resource "aws_kms_key" "pii" {
  description             = "CMK for PII data (customer data, authentication)"
  deletion_window_in_days = 30   # At least 14 days

  # Automatic annual rotation of key material
  enable_key_rotation = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EnableIAMUserPermissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${var.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "AllowAppUsage"
        Effect = "Allow"
        Principal = {
          AWS = aws_iam_role.app.arn
        }
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Resource = "*"
      },
      {
        Sid    = "AllowCloudTrailUsage"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action = [
          "kms:GenerateDataKey*",
          "kms:DescribeKey"
        ]
        Resource = "*"
      }
    ]
  })

  tags = {
    data-class  = "pii"
    pillar      = "security"
    managed-by  = "terraform"
  }
}

resource "aws_kms_alias" "pii" {
  name          = "alias/cmk-pii"
  target_key_id = aws_kms_key.pii.key_id
}

S3 with CMK (SSE-KMS)

resource "aws_s3_bucket" "customer_data" {
  bucket = "myorg-customer-data-prod"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "customer_data" {
  bucket = aws_s3_bucket.customer_data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.pii.arn  # CMK, not AES256
    }
    bucket_key_enabled = true  # Reduces KMS API calls and costs
  }
}

# Non-Compliant: SSE-S3 (AES256) for PII data
# resource "aws_s3_bucket_server_side_encryption_configuration" "bad" {
#   rule {
#     apply_server_side_encryption_by_default {
#       sse_algorithm = "AES256"  # AWS-managed key – no CMK!
#     }
#   }
# }

RDS with Encryption at Rest

resource "aws_db_instance" "main" {
  identifier        = "prod-db"
  engine            = "postgres"
  engine_version    = "15.4"
  instance_class    = "db.t3.medium"
  storage_encrypted = true              # Encryption enabled
  kms_key_id        = aws_kms_key.pii.arn  # CMK (not default KMS)

  # Additional security settings
  publicly_accessible    = false        # Never public
  deletion_protection    = true         # Protection against accidental deletion
  backup_retention_period = 7           # 7-day backup retention

  # IAM authentication instead of password (for apps)
  iam_database_authentication_enabled = true
}

EBS Encryption by Default

# EBS encryption by default for the entire account
resource "aws_ebs_encryption_by_default" "enabled" {
  enabled = true
}

# Set default KMS key for EBS to CMK
resource "aws_ebs_default_kms_key" "main" {
  key_arn = aws_kms_key.general.arn
}

TLS Configuration

ALB: Modern TLS Security Policy

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = "443"
  protocol          = "HTTPS"

  # Modern TLS policy: TLS 1.2 and 1.3, strong cipher suites
  ssl_policy      = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn = aws_acm_certificate.main.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# HTTP → HTTPS redirect (no plain HTTP!)
resource "aws_lb_listener" "http_redirect" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

CloudFront: Minimum TLS Policy

resource "aws_cloudfront_distribution" "main" {
  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.cdn.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"  # No TLS 1.0 or 1.1
  }
}

For ALB security policies (as of 2025):

  • Recommended: ELBSecurityPolicy-TLS13-1-2-2021-06 (TLS 1.2 and 1.3)

  • Minimum: ELBSecurityPolicy-2016-08 (TLS 1.2 only)

  • Prohibited: Policies that allow TLS 1.0 or 1.1

TLS 1.3 cipher suites (automatically negotiated): * TLS_AES_128_GCM_SHA256 * TLS_AES_256_GCM_SHA384 * TLS_CHACHA20_POLY1305_SHA256

Certificate Management

AWS Certificate Manager (ACM)

ACM manages TLS certificates fully automatically (issuance, renewal, deployment on ALB/CloudFront):

# ACM certificate with automatic renewal
resource "aws_acm_certificate" "main" {
  domain_name               = "*.example.com"
  subject_alternative_names = ["example.com"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true  # Zero-downtime renewal
  }
}

# DNS validation via Route 53
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = aws_route53_zone.main.zone_id
  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
  ttl     = 60
}

Let’s Encrypt (for internal endpoints)

For internal services that do not use ACM (e.g., HashiCorp Vault, internal APIs):

  • Certbot or cert-manager (Kubernetes) for automatic Let’s Encrypt certificates

  • Wildcard certificates via DNS-01 challenge (for internal domains)

  • ACME protocol for automatic renewal

Anti-Patterns

  • AES256 for PII: SSE-S3 (AES256) means AWS manages the key – no CMK ownership.

  • Single KMS key for everything: Loss of one key should not affect all data categories.

  • No bucket_key_enabled: Without bucket key, high KMS API costs are incurred.

  • Manage certificates manually: ACM for all AWS services; no manual upload without rotation.

  • TLS 1.0/1.1 for "compatibility": TLS 1.2+ has been universally supported since 2022.

Metrics

  • Share of PII/financial data stores with CMK: Target 100%

  • KMS key rotation enabled: Target 100% of all CMKs

  • TLS 1.2+ on all external endpoints: Target 100%

  • ACM certificates with expiry > 30 days: Target 100% (ACM renews automatically)

  • EBS encryption by default: Target: Enabled in all accounts