Ravindra BagaleCourses & study guides Track your progress

Guides

AWS IAM Policy Examples (JSON) Explained Line by Line

An AWS IAM policy is a JSON document with a "Version" (always "2012-10-17") and one or more "Statement" blocks. Each statement says Effect (Allow or Deny), Action (such as s3:GetObject), Resource (an ARN) and, optionally, a Condition (such as "only with MFA"). IAM denies everything by default, and an explicit Deny always wins. Below are seven tested examples you can copy, change and check in the Policy Simulator.

The shape of every policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadInvoices",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::rb-invoices-2026/*",
      "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } }
    }
  ]
}

A live example first

The accounts team needs to read invoice PDFs from one bucket, rb-invoices-2026, and nothing else. A beginner attaches AmazonS3FullAccess. That lets them read, change and delete every bucket in the account. Here is the right policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListTheBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::rb-invoices-2026"
    },
    {
      "Sid": "ReadTheFiles",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::rb-invoices-2026/*"
    }
  ]
}

Test it in the CLI with a user or role that has only this policy:

aws s3 ls s3://rb-invoices-2026/                 # works
aws s3 cp s3://rb-invoices-2026/inv-1001.pdf .   # works
aws s3 rm s3://rb-invoices-2026/inv-1001.pdf     # AccessDenied
aws s3 ls s3://rb-payroll-2026/                  # AccessDenied

Friends, notice the two resources: the bucket itself for listing, and bucket/* for the files. Missing one of them is the most common S3 policy mistake.

How does IAM decide?

How IAM evaluates a request A request is checked against policies: explicit deny wins, then an allow is needed, otherwise the default is deny. How IAM evaluates a request Requestaction + ARNDeny?explicit winsAllow?matchingConditionMFA/regionResultallow/deny you Default is deny. An explicit Deny always beats an Allow

IAM checks every request: explicit Deny wins, an Allow must match, otherwise it is denied by default.

  1. Every request starts as denied.
  2. If any policy has a matching explicit Deny, the answer is Deny. Stop.
  3. If a matching Allow exists (and its conditions are true), the answer is Allow.
  4. Otherwise, it stays denied.

In bigger setups, Service Control Policies (SCPs) in AWS Organizations, permission boundaries and resource policies (like S3 bucket policies) also take part, but the rule "explicit deny wins" stays the same.

Seven examples, one per real need

1. Upload only into one folder (a web app storing user files)

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:PutObject"],
    "Resource": "arn:aws:s3:::rb-app-uploads/users/*"
  }]
}

Attach it to the EC2 or ECS role of the app, not to a user with access keys.

2. Start and stop only EC2 instances tagged for a team

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*" },
    { "Effect": "Allow",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "arn:aws:ec2:ap-south-1:123456789012:instance/*",
      "Condition": { "StringEquals": { "aws:ResourceTag/team": "training" } } }
  ]
}

Describe* actions do not support resource-level limits, so they use "*".

3. Require MFA for everything except setting up MFA

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyWithoutMFA",
    "Effect": "Deny",
    "NotAction": ["iam:CreateVirtualMFADevice", "iam:EnableMFADevice",
                  "iam:ListMFADevices", "iam:GetUser", "sts:GetSessionToken"],
    "Resource": "*",
    "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } }
  }]
}

4. Deny everything outside the Mumbai region

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "NotAction": ["iam:*", "sts:*", "cloudfront:*", "route53:*", "support:*", "budgets:*"],
    "Resource": "*",
    "Condition": { "StringNotEquals": { "aws:RequestedRegion": "ap-south-1" } }
  }]
}

Global services are excluded with NotAction, so they keep working.

5. Read one secret from Secrets Manager (a Spring Boot or Node.js app)

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "secretsmanager:GetSecretValue",
    "Resource": "arn:aws:secretsmanager:ap-south-1:123456789012:secret:prod/orders-db-*"
  }]
}

The -* at the end matches the random suffix AWS adds to secret ARNs.

6. Write to one DynamoDB table (a Python Lambda function)

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:UpdateItem"],
    "Resource": "arn:aws:dynamodb:ap-south-1:123456789012:table/Orders"
  }]
}

7. A trust policy: let EC2 use a role

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}

This goes on the role, not as a permission. It says who may wear the role. Examples 1 to 6 say what the role may do.

Test before you attach

  1. Open IAM → Policy Simulator (policysim.aws.amazon.com).
  2. Choose the user, group or role, or paste a new policy.
  3. Pick the service and actions, such as S3 → GetObject, DeleteObject.
  4. Enter the resource ARN.
  5. Click Run simulation and read allowed or denied for each action.
  6. After attaching, use IAM Access Analyzer to find public or cross-account access and to generate policies from real CloudTrail activity.

The same idea on other clouds

Cloud Policy language Example
AWS IAM JSON (Effect, Action, Resource) s3:GetObject on a bucket ARN
Azure RBAC role definitions (Actions, scopes) Storage Blob Data Reader on one container
Google Cloud IAM roles bound to members on a resource roles/​storage.​objectViewer on one bucket

Real example: why least privilege matters

In the Capital One breach (2019), an attacker used a server-side request forgery (SSRF) weakness in a misconfigured web application firewall on EC2 to get the instance role's temporary credentials. US court documents and later analysis show the role could list and read a very large number of S3 buckets, and data on about 100 million people in the US and 6 million in Canada was taken. A role limited to the one or two buckets that firewall really needed, plus IMDSv2, would have greatly reduced the damage.

Mistakes to avoid

  1. "Action": "*" and "Resource": "*" for an app. Name the exact actions and ARNs.
  2. Forgetting the bucket ARN for ListBucket. You get AccessDenied on aws s3 ls.
  3. Putting access keys on servers. Use roles with trust policies (example 7).
  4. Editing JSON without testing. Run the Policy Simulator first.

Summary table

Key Meaning Example
Version Policy language version "2012-10-17"
Effect Allow or Deny "Allow"
Action API calls "s3:GetObject"
Resource ARNs the actions apply to "arn:​aws:​s3:::​bucket/*"
Condition Extra rules MFA, region, tags
Principal Who (only in trust and resource policies) "ec2.​amazonaws.​com"

Quick quiz: check what you learned

  1. What happens if no policy allows an action?

    1. It is denied by default
    2. It is allowed by default
    3. It asks the root user
    4. It logs and then allows
    Show answer

    A. IAM starts from an implicit deny.

  2. Which resource ARN does s3:ListBucket need?

    1. The bucket ARN itself
    2. The object path ARN /*
    3. The IAM user ARN
    4. The account root ARN
    Show answer

    A. ListBucket acts on the bucket, not on objects.

  3. What does a trust policy define?

    1. Who may assume the role
    2. Which S3 files exist
    3. How much the role costs
    4. Which region is used
    Show answer

    A. Trust policies set the principals allowed to assume a role.

  4. If an Allow and a Deny both match, what wins?

    1. The explicit Deny
    2. The newest policy
    3. The Allow statement
    4. The longest policy
    Show answer

    A. An explicit Deny always overrides any Allow.

Frequently asked questions

What are the parts of an IAM policy?

"Version" (always "2012-10-17"), and one or more "Statement" blocks, each with "Effect" (Allow or Deny), "Action", "Resource" and an optional "Condition".

What wins if Allow and Deny both match?

An explicit Deny always wins. If nothing allows the action, it is denied by default.

Why do I need two Resource ARNs for S3?

ListBucket works on the bucket ARN (arn:aws:s3:::name), while GetObject and PutObject work on objects (arn:aws:s3:::name/*).

What is a trust policy?

A policy on a role that says who may assume it, such as the EC2 service or a GitHub OIDC provider. Permission policies say what the role can do.

How do I test a policy before using it?

Use the IAM Policy Simulator in the console, or try the action with the AWS CLI using a test role.

Is AdministratorAccess OK for learning?

Use it only on your own lab account with MFA. In real work, start from AWS managed job-function policies and narrow them down.