Subchapter 1.28
rules/terraform-aws.mdMarkdown5 KBView on GitHub
Security best practices for AWS Terraform configurations to prevent common misconfigurations.
Incorrect (bucket without server-side encryption):
resource "aws_s3_bucket" "bucket" {
bucket = "my-bucket"
}Correct (bucket-level KMS encryption via ):
aws_s3_bucket_server_side_encryption_configurationresource "aws_s3_bucket" "bucket" {
bucket = "my-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "pass" {
bucket = aws_s3_bucket.bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.example.arn
}
bucket_key_enabled = true
}
}Note:
aws_s3_bucket_objectis deprecated in AWS provider 4+. Useaws_s3_objectfor individual objects, and configure encryption at the bucket level withaws_s3_bucket_server_side_encryption_configurationso all objects inherit it automatically.
Incorrect (wildcard admin):
resource "aws_iam_policy" "fail" {
policy = <<POLICY
{"Version":"2012-10-17","Statement":[{"Action":"*","Effect":"Allow","Resource":"*"}]}
POLICY
}Correct (least privilege):
resource "aws_iam_policy" "pass" {
policy = <<POLICY
{"Version":"2012-10-17","Statement":[{"Action":["s3:GetObject*"],"Effect":"Allow","Resource":"arn:aws:s3:::bucket/*"}]}
POLICY
}Incorrect (wildcard AssumeRole):
resource "aws_iam_role" "fail" {
assume_role_policy = <<POLICY
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"sts:AssumeRole"}]}
POLICY
}Correct (restricted AssumeRole):
resource "aws_iam_role" "pass" {
assume_role_policy = <<POLICY
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"sts:AssumeRole"}]}
POLICY
}Incorrect (EBS):
resource "aws_ebs_volume" "fail" {
availability_zone = "us-west-2a"
encrypted = false
}Correct (EBS):
resource "aws_ebs_volume" "pass" {
availability_zone = "us-west-2a"
encrypted = true
}Incorrect (RDS no backup):
resource "aws_db_instance" "fail" { backup_retention_period = 0 }Correct (RDS with backup):
resource "aws_db_instance" "pass" { backup_retention_period = 35 }Incorrect (DynamoDB):
resource "aws_dynamodb_table" "fail" {
name = "Table"; hash_key = "Id"
attribute { name = "Id"; type = "S" }
}Correct (DynamoDB with CMK):
resource "aws_dynamodb_table" "pass" {
name = "Table"; hash_key = "Id"
attribute { name = "Id"; type = "S" }
server_side_encryption { enabled = true; kms_key_arn = "arn:aws:kms:..." }
}Incorrect (SQS/SNS):
resource "aws_sqs_queue" "fail" { name = "queue" }
resource "aws_sns_topic" "fail" {}Correct (SQS/SNS encrypted):
resource "aws_sqs_queue" "pass" { name = "queue"; sqs_managed_sse_enabled = true }
resource "aws_sns_topic" "pass" { kms_master_key_id = "alias/aws/sns" }Incorrect (public SSH):
resource "aws_security_group_rule" "fail" {
type = "ingress"; protocol = "tcp"; from_port = 22; to_port = 22
cidr_blocks = ["0.0.0.0/0"]
}Correct (restricted CIDR):
resource "aws_security_group_rule" "pass" {
type = "ingress"; protocol = "tcp"; from_port = 22; to_port = 22
cidr_blocks = ["10.0.0.0/8"]
}Incorrect (public IP):
resource "aws_instance" "fail" {
ami = "ami-12345"; instance_type = "t3.micro"
associate_public_ip_address = true
}Correct (no public IP):
resource "aws_instance" "pass" {
ami = "ami-12345"; instance_type = "t3.micro"
associate_public_ip_address = false
}Incorrect (KMS no rotation):
resource "aws_kms_key" "fail" { enable_key_rotation = false }Correct (KMS with rotation):
resource "aws_kms_key" "pass" { enable_key_rotation = true }Incorrect (CloudTrail):
resource "aws_cloudtrail" "fail" { name = "trail"; s3_bucket_name = "bucket" }Correct (CloudTrail encrypted):
resource "aws_cloudtrail" "pass" {
name = "trail"; s3_bucket_name = "bucket"; kms_key_id = aws_kms_key.key.arn
}Incorrect (hardcoded):
provider "aws" {
region = "us-west-2"; access_key = "AKIAEXAMPLE"; secret_key = "secret"
}Correct (external credentials):
provider "aws" {
region = "us-west-2"; shared_credentials_file = "~/.aws/creds"; profile = "myprofile"
}