Terraform Advanced — Least Privilege IAM
Give Terraform only the permissions it needs — nothing more.
The Anti-Pattern
# ❌ Anti-pattern: admin access for Terraform
resource "aws_iam_role_policy_attachment" "terraform" {
role = aws_iam_role.terraform.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
Scoped Permissions
# ✅ Best practice: scoped to what Terraform actually manages
resource "aws_iam_policy" "terraform" {
name = "terraform-deploy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "ManageVPC"
Effect = "Allow"
Action = [
"ec2:CreateVpc", "ec2:DeleteVpc", "ec2:DescribeVpcs",
"ec2:CreateSubnet", "ec2:DeleteSubnet", "ec2:DescribeSubnets",
"ec2:CreateSecurityGroup", "ec2:DeleteSecurityGroup",
]
Resource = "*"
Condition = {
StringEquals = {
"aws:RequestedRegion" = ["us-east-1", "us-west-2"]
}
}
},
{
Sid = "ManageState"
Effect = "Allow"
Action = [
"s3:GetObject", "s3:PutObject", "s3:ListBucket",
"dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem",
]
Resource = [
"arn:aws:s3:::my-tf-state/*",
"arn:aws:dynamodb:us-east-1:*:table/tf-locks",
]
}
]
})
}
Separate Roles Per Environment
terraform-dev → only manage resources in dev account
terraform-staging → only manage resources in staging account
terraform-prod → only manage resources in prod account
Use iamlive to Discover Permissions
# Record actual API calls during terraform apply
iamlive --mode proxy --output-file policy.json
# Use as HTTP proxy
HTTPS_PROXY=http://127.0.0.1:10080 terraform apply
# iamlive generates the minimal IAM policy
💡 Production Tip: Run
iamliveduring a full apply cycle in dev, then use the generated policy as your baseline for production.
What’s Next?
Practice security patterns.