Cloud

IAM Roles vs Policies: AWS Permissions Without the Headache

Understand AWS IAM roles, policies, users, and groups. Learn how the policy evaluation engine works, when to use identity-based vs resource-based policies, and how to implement least privilege.

A
Abhishek Patel8 min read

Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

IAM Roles vs Policies: AWS Permissions Without the Headache
IAM Roles vs Policies: AWS Permissions Without the Headache

Why AWS Permissions Feel So Complicated

IAM (Identity and Access Management) is the permission system that controls who can do what in your AWS account. It's also the service that causes the most confusion, the most security incidents, and the most "why can't I access this resource" Slack messages. The core concepts aren't hard, but AWS has layered on so many features over the years that the documentation reads like a legal contract.

Here's the thing: once you understand the difference between roles and policies, and how the policy evaluation engine actually works, IAM becomes predictable. You'll stop copy-pasting AdministratorAccess onto everything and start building permissions that follow least privilege without making your team's life miserable.

What Is AWS IAM?

Definition: AWS IAM (Identity and Access Management) is the service that controls authentication and authorization for every API call in your AWS account. It determines who can call which APIs on which resources under what conditions, with no additional charge.

IAM has four building blocks: users, groups, roles, and policies. Everything else -- instance profiles, permission boundaries, service control policies -- is built on top of these four.

IAM Building Blocks: Step by Step

Step 1: Understand Principals

A principal is an entity that can make API calls. In AWS, principals are:

  • IAM Users -- long-lived credentials (access key + secret key) tied to a person or service
  • IAM Roles -- temporary credentials assumed by users, services, or other AWS accounts
  • AWS Services -- EC2 instances, Lambda functions, ECS tasks that assume roles
  • Federated Users -- identities from external providers (Okta, Google Workspace) mapped to roles

Step 2: Create Policies

A policy is a JSON document that defines permissions. Every policy has the same structure:

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

Each statement specifies an Effect (Allow or Deny), the Actions permitted, and the Resources they apply to. You can add Conditions for fine-grained control -- restricting by IP, time, MFA status, tags, or dozens of other context keys.

Step 3: Attach Policies to Principals

Policies don't do anything on their own. You attach them to users, groups, or roles. A single principal can have up to 10 managed policies attached, plus inline policies (which are embedded directly in the principal's configuration).

Step 4: Use Roles Instead of Access Keys

This is the single most important IAM practice. Roles provide temporary credentials that rotate automatically. Access keys are permanent until you rotate them manually -- and nobody does. Every EC2 instance, Lambda function, and ECS task should use a role, never hardcoded keys.

Pro tip: Run aws iam generate-credential-report regularly. It shows every IAM user, when their access keys were last used, and whether MFA is enabled. Any access key older than 90 days is a security risk.

Roles vs Policies: The Core Distinction

ConceptRolePolicy
What it isAn identity that can be assumedA document defining permissions
CredentialsTemporary (STS tokens, 1-12 hours)N/A -- policies don't have credentials
AttachmentHas policies attached to itAttached to users, groups, or roles
TrustHas a trust policy defining who can assume itNo trust concept
Use caseEC2 instance profiles, cross-account access, Lambda executionDefining what actions are allowed on what resources

Think of it this way: a role is a hat you put on. A policy is the list of things you're allowed to do while wearing that hat. A single role can have multiple policies, and a single policy can be attached to multiple roles.

Identity-Based vs Resource-Based Policies

AWS has two categories of policies, and mixing them up causes most permission bugs:

Identity-Based Policies

Attached to IAM users, groups, or roles. They say "this principal can do X on resource Y."

{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

Resource-Based Policies

Attached directly to a resource (S3 bucket policy, SQS queue policy, KMS key policy). They say "principal X can do Y on this resource." The key difference: resource-based policies have a Principal field.

{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::123456789012:role/AppRole" },
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

For cross-account access, resource-based policies are essential. An identity-based policy in Account A can grant access to a bucket in Account B, but only if Account B's bucket policy also allows it. Both sides must agree.

How the Policy Evaluation Engine Works

AWS evaluates permissions in a specific order. Understanding this order eliminates 90% of IAM debugging pain:

  1. Explicit Deny -- if any policy says Deny, the request is denied. Period. Deny always wins.
  2. Service Control Policies (SCPs) -- organization-level guardrails. If the SCP doesn't allow it, it's denied.
  3. Permission Boundaries -- the maximum permissions an entity can have. If the boundary doesn't include the action, it's denied.
  4. Identity-Based Policies -- the policies attached to the user/role making the request.
  5. Resource-Based Policies -- if the resource has a policy that explicitly allows the principal, access is granted (even without an identity-based allow for same-account access).
  6. Implicit Deny -- if nothing explicitly allows it, the request is denied.

Watch out: The most common IAM mistake is not understanding implicit deny. If you create a role with no policies attached, it can't do anything -- not because there's a deny rule, but because there's no allow rule. Everything starts as denied.

EC2 Instance Profiles and Service Roles

An instance profile is a container for an IAM role that lets EC2 instances assume that role. When you "attach a role to an EC2 instance," you're actually creating an instance profile and associating it with the instance. The EC2 metadata service at 169.254.169.254 then provides temporary credentials to any process running on the instance.

# From inside an EC2 instance
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole
# Returns temporary AccessKeyId, SecretAccessKey, and Token

Lambda, ECS, and other services have their own mechanism for role assumption, but the concept is identical: the service assumes the role, gets temporary credentials, and uses them for API calls.

Cross-Account Access Pattern

The standard pattern for cross-account access:

  1. In Account B (the target), create a role with a trust policy allowing Account A to assume it
  2. In Account A (the source), grant the user or role permission to call sts:AssumeRole on Account B's role
  3. The source principal calls AssumeRole, gets temporary credentials, and uses them to access Account B's resources
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111111111111:root" },
      "Action": "sts:AssumeRole",
      "Condition": { "StringEquals": { "sts:ExternalId": "unique-id-here" } }
    }
  ]
}

Tools and Pricing

IAM itself is free -- no charge for users, groups, roles, or policies. The tools that help you manage IAM effectively:

  • IAM Access Analyzer -- free, finds resources shared outside your account and generates least-privilege policies from CloudTrail logs
  • AWS Organizations + SCPs -- free, but requires organizational setup
  • CloudTrail -- first trail is free, additional trails $2.00 per 100,000 management events
  • Third-party tools -- Bridgecrew (now Prisma Cloud), Ermetic, and IAM Pulse offer policy analysis starting around $500/month for small teams

Frequently Asked Questions

What is the difference between an IAM role and an IAM user?

An IAM user has permanent long-lived credentials (password and/or access keys) tied to a single identity. An IAM role has no permanent credentials -- it issues temporary security tokens when assumed. Roles are preferred because temporary credentials automatically expire and don't need manual rotation.

When should I use inline policies vs managed policies?

Use managed policies for reusable permissions that apply to multiple principals. Use inline policies only for permissions that are tightly coupled to a single principal and should be deleted when that principal is deleted. AWS recommends managed policies for almost everything because they're easier to audit and reuse.

How do I debug "Access Denied" errors in AWS?

Start with CloudTrail -- find the denied API call and check the error code. Then use IAM Policy Simulator to test the principal's effective permissions. Check for explicit denies in SCPs, permission boundaries, and resource-based policies. Nine times out of ten, it's a missing resource ARN or a condition key mismatch.

What is a permission boundary?

A permission boundary is a managed policy that sets the maximum permissions an IAM entity can have. Even if an identity-based policy grants s3:*, if the permission boundary only allows s3:GetObject, the entity can only read objects. Boundaries are useful for delegated administration -- letting developers create roles without exceeding predefined limits.

Should I use AWS SSO or IAM users for human access?

Use AWS IAM Identity Center (formerly AWS SSO) for all human access. It integrates with your existing identity provider, provides temporary credentials, and eliminates the need for IAM users entirely. IAM users should only exist for legacy service accounts that can't use roles, and even those should be migrated.

How many policies can I attach to a single role?

You can attach up to 10 managed policies to a single IAM role. You can also add inline policies, but the total size of all policies (managed + inline) attached to a role cannot exceed 10,240 characters. If you hit this limit, consolidate your policies or use fewer, broader statements.

Build Permissions Intentionally

Start every role with zero permissions and add only what's needed. Use IAM Access Analyzer to generate policies from actual CloudTrail usage. Never use * in the Resource field for production roles. Set up SCPs to prevent the most dangerous actions (leaving a region unrestricted, creating IAM users with console access) at the organization level. IAM isn't glamorous, but getting it right is the difference between a secure AWS account and a breach headline.

A

Written by

Abhishek Patel

Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.

Related Articles

Enjoyed this article?

Get more like this in your inbox. No spam, unsubscribe anytime.

Comments

Loading comments...

Leave a comment

Stay in the loop

New articles delivered to your inbox. No spam.