IAM hardening field manual
Reasoning behind every checklist item, plus more Terraform.
Severity-tagged checklist we run during security engagements. AWS and GCP variants inline. Copy what applies, skip what does not.
Template - copy freely
Each item is tagged with a severity (Critical, High, Medium, Low). Fix Critical and High within one sprint. Fix Medium within a quarter. Low is backlog unless you find a specific reason to prioritize.
Inline Terraform snippets are illustrative. Adapt to your module structure. See the field manual for the reasoning behind each item.
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
data "aws_iam_policy_document" "github_deploy_trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:yourorg/yourrepo:ref:refs/heads/main"]
}
}
}
resource "aws_iam_role" "github_deploy" {
name = "github-deploy"
assume_role_policy = data.aws_iam_policy_document.github_deploy_trust.json
max_session_duration = 3600
}
# Attach a least-privilege policy - do NOT use AdministratorAccess.
resource "aws_iam_role_policy_attachment" "github_deploy" {
role = aws_iam_role.github_deploy.name
policy_arn = aws_iam_policy.deploy_policy.arn
}
*:*. No entity has AdministratorAccess as a baseline.s3:GetObject, not s3:*).*).iam:PassRole limited to specific target roles, not *.data "aws_iam_policy_document" "deploy" {
statement {
sid = "EcsDeploy"
effect = "Allow"
actions = [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
]
resources = ["arn:aws:ecs:us-east-1:${var.account_id}:service/prod-cluster/app"]
}
statement {
sid = "EcrPush"
effect = "Allow"
actions = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
]
resources = ["arn:aws:ecr:us-east-1:${var.account_id}:repository/app"]
}
statement {
sid = "PassTaskRole"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [aws_iam_role.ecs_task.arn]
}
}
resource "aws_iam_policy" "deploy_policy" {
name = "github-deploy-policy"
policy = data.aws_iam_policy_document.deploy.json
}
resource "google_iam_workload_identity_pool" "github" {
workload_identity_pool_id = "github-pool"
}
resource "google_iam_workload_identity_pool_provider" "github" {
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.repository" = "assertion.repository"
}
attribute_condition = "assertion.repository == 'yourorg/yourrepo'"
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
resource "google_service_account" "github_deploy" {
account_id = "github-deploy"
display_name = "GitHub Actions deploy"
}
resource "google_service_account_iam_binding" "github_impersonate" {
service_account_id = google_service_account.github_deploy.name
role = "roles/iam.workloadIdentityUser"
members = [
"principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/yourorg/yourrepo",
]
}
Some items often appear on IAM checklists that we do not include because they are not worth the cost for most SMB/mid-market companies:
A team that has checked off everything Critical and High in this list has closed the majority of IAM-related attack paths at a Series A / B stage. Medium items close the rest. Low items are polish. See the field manual for full reasoning on every item.
Reasoning behind every checklist item, plus more Terraform.
Where this checklist’s absence shows up - F-02 is a failure against item 3.
Audits, threat models, IAM work.