Understanding AWS IAM Policies: A Practical Guide

A developer's guide to the core components of an AWS IAM policy. Learn how to read and write policies by understanding Effect, Principal, Action, Resource, and Condition.

In AWS, everything revolves around permissions. IAM (Identity and Access Management) is the service that controls who can do what in your AWS account. The way you define these permissions is with IAM Policies.

An IAM policy is a JSON document that explicitly lists permissions. Understanding the structure of this document is a fundamental skill for any AWS developer, as it's the key to building secure and robust applications.

The Structure of an IAM Policy

At its core, every IAM policy is a JSON object containing one or more statements. Each statement is a formal declaration of a single permission.

Let's break down the main components of a statement:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-bucket/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

1. Effect: Allow or Deny

This is the simplest part. The Effect is either Allow or Deny.

  • Allow: Grants the permission.
  • Deny: Explicitly forbids the permission.

An explicit Deny always overrides an Allow. This is a crucial security principle. If a user has a policy that allows access to S3 but another policy that denies it, the Deny will win.

2. Principal (Only in Resource-Based Policies)

The Principal is the user, account, service, or other entity that is allowed or denied access.

Important: The Principal element is only used in resource-based policies (like an S3 bucket policy or an IAM role's trust policy). It is not used in identity-based policies (policies you attach directly to a user or a group).

"Principal": { "AWS": "arn:aws:iam::123456789012:root" } // A specific AWS account
"Principal": { "Service": "ec2.amazonaws.com" } // An AWS service

3. Action: What You Can Do

The Action describes the specific API call(s) that are allowed or denied. Actions are always prefixed with the service name.

  • s3:GetObject: The action to read an object from S3.
  • dynamodb:PutItem: The action to write an item to DynamoDB.
  • ec2:RunInstances: The action to launch a new EC2 instance.

You can use wildcards (*) to grant multiple permissions.

  • s3:*: All actions for S3.
  • ec2:Describe*: All actions that start with Describe in EC2.

4. Resource: What You Can Do It To

The Resource specifies the object or objects that the statement covers. Resources are always specified by their ARN (Amazon Resource Name).

An ARN is a globally unique identifier for an AWS resource.

arn:partition:service:region:account-id:resource-id
arn:aws:s3:::my-app-bucket/*
arn:aws:dynamodb:us-east-1:123456789012:table/MyTable

Like with Action, you can use wildcards in the Resource element.

5. Condition (Optional): When You Can Do It

The Condition element is an optional but powerful way to add fine-grained control. It lets you specify circumstances under which the policy is in effect.

Example: Only allow access if the request is coming from a specific IP address range.

"Condition": {
  "IpAddress": {
    "aws:SourceIp": "203.0.113.0/24"
  }
}

Example: Only allow writing an S3 object if it has a specific tag.

"Condition": {
  "StringEquals": {
    "s3:x-amz-object-tagging": "department=finance"
  }
}

Putting It All Together: A Real-World Example

Let's write a policy that we would attach to an IAM user. This policy will grant the user read-only access to a specific S3 bucket.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowListBucket",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::my-data-bucket"
        },
        {
            "Sid": "AllowReadObjects",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-data-bucket/*"
        }
    ]
}

This policy has two statements:

  1. The first statement allows the user to list the contents of the my-data-bucket.
  2. The second statement allows the user to get (read) any object inside that bucket.

This separation is necessary because s3:ListBucket is an action on the bucket itself, while s3:GetObject is an action on the objects within the bucket.

Conclusion

IAM policies are the foundation of security in AWS. By mastering the five key components—Effect, Principal, Action, Resource, and Condition—you can move beyond using overly permissive, AWS-managed policies and start writing fine-grained, least-privilege policies that grant exactly the permissions needed, and no more. This is a critical skill for building secure and professional applications in the cloud.