So while it's tempting to just give the terraform account admin access to everything, it's not a good security policy. It's still worth trying to make the permissions as restrictive as you reasonably can, for this user.
I split up my permissions as follows. The arns etc are examples only and should not be copied verbatim (eg /bucket would be the name of your actual S3 bucket).
This is what ended up working for me:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "List",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::bucket"]
},
{
"Sid": "ManageObjects",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject"],
"Resource": ["arn:aws:s3:::bucket/*"]
},
{
"Sid": "SpecificTable",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:DeleteItem", "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:*:*:table/xxx"
}
]
}
This is the initial policy I've started with, for provisioning infra via terraform incl compute, databases, and pushing images to ECR:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformCore",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ec2:*",
"ecr:BatchCheckLayerAvailability",
"rds:DeleteDBSubnetGroup",
"rds:CreateDBInstance",
"rds:CreateDBSubnetGroup",
"rds:DeleteDBInstance",
"rds:DescribeDBSubnetGroups",
"rds:DescribeDBInstances",
"rds:ListTagsForResource",
"rds:ModifyDBInstance",
"iam:CreateServiceLinkedRole",
"rds:AddTagsToResource"
],
"Resource": "*"
},
{
"Sid": "PushImagesToECR",
"Effect": "Allow",
"Action": "ecr:*",
"Resource": "arn:aws:ecr:*:*:repository/xxx"
},
{
"Sid": "MaxEC2Size",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": ["arn:aws:ec2:*:*:instance/*"],
"Condition": {
"ForAnyValue:StringNotLike": {
"ec2:InstanceType": "t2.micro"
}
}
}
]
}
... where nnn is your account number and xxx is the relevant resource name. Also obviously modify regions etc as needed.
^ My fave part of the above is not letting this user provision any machines bigger than a t2.micro, lol
We all know AWS LURVES to get paid.
Finally, this is the policy to allow tf to provision an instance profile for a bastion host that needs to be able to connect to other resources (I abandoned this project after this point, so ymmv).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ConfigureBastionIAM",
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:GetInstanceProfile",
"iam:DeletePolicy",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:AddRoleToInstanceProfile",
"iam:ListInstanceProfilesForRole",
"iam:ListAttachedRolePolicies",
"iam:DeleteRole",
"iam:TagRole",
"iam:PassRole",
"iam:GetPolicyVersion",
"iam:GetPolicy",
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:CreateInstanceProfile",
"iam:DeleteInstanceProfile",
"iam:ListPolicyVersions",
"iam:AttachRolePolicy",
"iam:CreatePolicy",
"iam:RemoveRoleFromInstanceProfile"
],
"Resource": "*"
}
]
}