Skip to content

IAM policy examples: the 12 policies every AWS account ends up needing

Twelve IAM policy examples that show up in practically every production AWS account — app roles scoped to one bucket, region lockdowns, tag-based EC2 control, MFA enforcement, and more, with working JSON.

Every AWS account that survives past its first year converges on roughly the same dozen IAM policies: an app role scoped to one bucket, a read-only auditor, a region lockdown, a tag-based EC2 policy for developers, an MFA gate, and a handful of others. Below are the twelve we end up writing in nearly every environment, with working JSON for the ones people most often get wrong. Copy them as starting points, then tighten the resources to your ARNs.

What’s happening

IAM policies fail in two directions. Too loose, and the account accumulates Action: "*" grants that pass no audit and turn any leaked credential into a full compromise. Too tight, and every deploy generates an Access Denied ticket until someone “fixes” it with a wildcard — arriving at too loose by a longer road. The escape is a small library of proven shapes: policies where the actions, resources, and conditions are already balanced, so each new requirement is a variable substitution rather than a blank JSON editor. AWS evaluation rules reward this — explicit deny always wins, Condition blocks make grants situational, and resource ARNs do the real scoping work. The policy reference in the IAM documentation covers the grammar; the shapes below cover the practice.

What to check first

Before writing anything new, inventory what exists — most accounts already contain three half-versions of every policy below:

  • Count customer-managed vs inline policies. Inline policies hide from reuse and review; anything used twice should be customer-managed.
  • Find the wildcards. Any policy with "Action": "*" on "Resource": "*" outside the admin role is the first thing to replace.
  • Check for AWS managed policies doing work they are too broad for. AmazonS3FullAccess on an app role is the classic.
  • Look at what is actually used. Last-accessed data tells you which grants are dead weight before you redesign.

How to verify

# Inventory customer-managed policies and where they are attached
aws iam list-policies --scope Local \
  --query 'Policies[].[PolicyName,AttachmentCount]' --output table

# Dump a policy's current default version
aws iam get-policy-version \
  --policy-arn arn:aws:iam::111122223333:policy/app-s3-scoped \
  --version-id v1

# Find inline policies on a role
aws iam list-role-policies --role-name app-role

Console path for attachment review: IAM > Policies > (policy) > Entities attached. Validate new JSON with the policy checks built into the console editor — it flags invalid actions and over-broad resources before you save.

The runbook

  1. App role scoped to one bucket prefix — the most-written policy in AWS. Note the split: ListBucket on the bucket, object actions on the prefix.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::prod-assets",
      "Condition": {"StringLike": {"s3:prefix": ["uploads/*"]}}
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::prod-assets/uploads/*"
    }
  ]
}
  1. Region lockdown deny — stops resources appearing in regions nobody monitors. Exempt the global services that authenticate through us-east-1.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "NotAction": ["iam:*", "sts:*", "organizations:*", "route53:*",
                    "cloudfront:*", "support:*", "budgets:*"],
      "Resource": "*",
      "Condition": {"StringNotEquals": {"aws:RequestedRegion": ["ca-central-1", "us-east-1"]}}
    }
  ]
}
  1. Tag-based EC2 start/stop for developers — operate only instances tagged to your team, without touching production.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ec2:StartInstances", "ec2:StopInstances", "ec2:RebootInstances"],
      "Resource": "arn:aws:ec2:*:111122223333:instance/*",
      "Condition": {"StringEquals": {"aws:ResourceTag/team": "platform"}}
    },
    {
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    }
  ]
}
  1. Read-only auditor — start from the AWS managed ReadOnlyAccess or the tighter SecurityAudit, then add a deny for data-plane reads (s3:GetObject, dynamodb:GetItem) if the auditor should see configuration but not customer data.
  2. Deny-unless-MFA — the BoolIfExists deny that makes MFA real rather than suggested; the full shape and its pitfalls are in our MFA enforcement guide.
  3. Self-service credentials — allow each IAM user to manage only their own password, access keys, and MFA device using arn:aws:iam::*:user/${aws:username} as the resource.
  4. Human assume-role-only policy — humans get sts:AssumeRole on named role ARNs and nothing else; all real permissions live on the roles.
  5. Finance and billing read — ce:Get*, ce:Describe*, budgets:View*, cur:Describe* for the people who ask about the bill, so they stop asking engineers.
  6. CI/CD deploy role — scoped to one service: the specific ECS service or Lambda functions, the specific ECR repo, iam:PassRole restricted to the one task role ARN. PassRole on * in a pipeline is privilege escalation in waiting.
  7. TLS-only bucket policy — a resource policy with "Effect": "Deny" when "aws:SecureTransport": "false", required by most compliance frameworks.
  8. Single-key KMS usage — kms:Decrypt, kms:GenerateDataKey on one key ARN, paired with a key policy that grants the account’s IAM layer.
  9. Break-glass admin — AdministratorAccess on a dedicated role with a trust policy requiring MFA, every use of which pages someone via a CloudTrail-driven alarm.

When this is the wrong move

Hand-rolling all twelve is wrong if the organization is about to adopt Organizations-wide guardrails — the region lockdown and several denies belong in SCPs, once, rather than in every account. And if humans still authenticate as IAM users, policies 5-7 are patches on a model worth replacing with Identity Center instead of perfecting.

Common mistakes

  • Putting s3:ListBucket on bucket/* or GetObject on the bare bucket ARN — each action binds to a specific ARN shape.
  • Forgetting the NotAction exemptions in the region deny, which locks everyone out of IAM and billing.
  • Granting iam:PassRole on * to CI roles — the quiet privilege escalation in most pipelines.
  • Using inline policies for anything reused; they cannot be versioned, shared, or reviewed in one place.

Keeping it under control

  • Keep the twelve as customer-managed policies in infrastructure-as-code, so drift is a diff, not archaeology.
  • Name policies by scope, not team folklore: app-prod-assets-rw, not steve-policy-2.
  • Review AttachmentCount quarterly and delete anything at zero.
  • Let unused-access findings drive pruning rather than intuition — grants nobody exercised in 90 days rarely deserve to stay.

Twelve shapes cover most of what an account needs; everything past that should be a deliberate exception with an owner and a reason. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.