AWS IAM Explained: Permissions, Roles, and Policies

In This Guide

  1. What Is IAM and Why It Matters
  2. Core Concepts: Users, Groups, Roles, and Policies
  3. Policies Deep Dive: Allow, Deny, and Condition
  4. Roles Explained: The Right Way to Grant Access
  5. The Least Privilege Principle in Practice
  6. Common IAM Patterns Every Developer Needs
  7. Troubleshooting Access Denied Errors
  8. IAM Best Practices Checklist
  9. Frequently Asked Questions

Key Takeaways

IAM is the most important service in AWS that developers consistently get wrong. Not because it is complicated in theory, but because the natural impulse when you hit an "Access Denied" error is to add more permissions — often far more than you need — until it works. That impulse, repeated across a codebase, creates a permission sprawl that makes your cloud environment brittle and your security posture weak.

This guide explains IAM clearly: what each concept means, why roles are better than users for application access, how policies are evaluated, and the patterns you should follow to avoid the most common mistakes.

What Is IAM and Why It Matters

AWS Identity and Access Management (IAM) is the system that controls who can authenticate to your AWS account and what actions they are authorized to take. Every API call in AWS — whether from the console, CLI, SDK, or another AWS service — is validated by IAM before it executes.

IAM is global — it is not regional. IAM users, groups, roles, and policies you create in one region are available across all regions in your account. This is different from most AWS services, which are region-scoped.

IAM is free. There is no charge for IAM users, roles, policies, or groups. You pay only for the AWS services that IAM principals call.

Why IAM matters for security: Every high-profile AWS breach of the last five years has involved IAM in some way — either overly permissive roles that allowed lateral movement, or leaked credentials that were not scoped to minimum permissions. Getting IAM right is the highest-ROI security investment in any AWS account.

Core Concepts: Users, Groups, Roles, and Policies

IAM has four core entities: users (human identities with permanent credentials), groups (collections of users), roles (assumable identities with temporary credentials), and policies (permission documents attached to any of the above).

IAM Users are permanent identities created for human beings who need long-term access to AWS. Each user gets a password for console access and/or access keys (access key ID + secret access key) for programmatic access. IAM users should only exist for human beings — never for applications, services, or automation.

IAM Groups are collections of IAM users that share the same permissions. Best practice: never attach policies directly to individual users. Create groups (Developers, ReadOnlyAuditors, DBAdmins), attach policies to groups, and add users to groups. This makes permission management scalable — when a developer leaves, you remove them from the group instead of hunting through individual policy attachments.

IAM Roles are identities that can be assumed by AWS services, users, or other accounts — and that provide temporary credentials (15 minutes to 12 hours). This is the correct mechanism for all non-human access: your Lambda function assumes a role, your EC2 instance assumes a role, a developer in account A assumes a role in account B. Roles are more secure than users for application access because temporary credentials cannot be leaked and used indefinitely.

IAM Policies are JSON documents that define what actions are allowed or denied on which resources. Policies are attached to users, groups, or roles to define their permissions. There are two types: AWS managed policies (pre-built by AWS, like AmazonS3ReadOnlyAccess) and customer managed policies (policies you write for your specific needs).

Policies Deep Dive: Allow, Deny, and Condition

An IAM policy is a JSON document with one or more Statement blocks. Each statement has an Effect (Allow or Deny), an Action (the API calls being permitted or denied), and a Resource (the specific ARN or ARN pattern the statement applies to). Conditions add optional constraints.

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

This policy allows reading from and listing the bucket, but explicitly denies deleting objects — even if another attached policy would otherwise allow it. Explicit Deny always wins.

Conditions add constraints based on request context: the source IP, time of day, MFA status, request region, or tag values on the resource. Example condition restricting access to specific IP ranges:

"Condition": {
  "IpAddress": {
    "aws:SourceIp": [
      "203.0.113.0/24",
      "198.51.100.0/24"
    ]
  }
}

Conditions are powerful for building fine-grained controls: require MFA for all sensitive API calls, restrict console access to corporate IP ranges, enforce tag-based access control so teams can only manage resources with their own team's tags.

Roles Explained: The Right Way to Grant Access

IAM roles should be used for all non-human access in AWS. The pattern is simple: create a role with a trust policy (who can assume it) and a permission policy (what they can do when they assume it). The service or user assumes the role and receives temporary credentials.

How Lambda uses a role:

  1. Create an IAM role with a trust policy allowing lambda.amazonaws.com to assume it
  2. Attach a permissions policy granting the specific S3, DynamoDB, or other permissions the function needs
  3. Assign the role to the Lambda function
  4. When Lambda executes, it automatically obtains temporary credentials from STS by assuming the role. Your code never needs to manage credentials — the AWS SDK picks them up automatically from the execution environment.

Cross-account roles allow principals in one AWS account to assume roles in another account. This is the correct pattern for giving a developer in your dev account access to read-only resources in your prod account: create a role in prod with a trust policy allowing the dev account, and the developer assumes the role from the dev account's CLI.

Never create IAM users and long-term access keys for automated processes. Access keys can be leaked in source code, environment variable files, log output, or error messages. Roles with temporary credentials cannot be leaked in a way that provides permanent access — the credentials expire automatically.

The Least Privilege Principle in Practice

Least privilege means granting only the specific permissions needed for the specific task, on the specific resources involved, for the minimum time required. Nothing more.

In practice, most developers violate least privilege in one of three ways:

1. Using wildcard actions: "Action": "s3:*" grants all S3 actions — including deleting buckets. If your Lambda function only reads from one bucket, it needs only s3:GetObject and s3:ListBucket on that specific bucket's ARN. Not s3:* on *.

2. Using wildcard resources: "Resource": "*" applies the permission to every resource of that type in the account. Grant permissions on specific ARNs whenever possible.

3. Attaching managed policies that include unnecessary permissions: AmazonS3FullAccess includes s3:DeleteBucket. AmazonDynamoDBFullAccess includes the ability to delete tables. Write narrow customer-managed policies for production workloads rather than relying on AWS managed policies.

Tools to help:

Common IAM Patterns Every Developer Needs

Pattern 1: Lambda function with S3 and DynamoDB access

{
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:GetObject", "s3:PutObject"
    ],
    "Resource": "arn:aws:s3:::my-bucket/*"
  },{
    "Effect": "Allow",
    "Action": [
      "dynamodb:GetItem", "dynamodb:PutItem",
      "dynamodb:UpdateItem", "dynamodb:Query"
    ],
    "Resource": "arn:aws:dynamodb:us-east-1:123456789:table/MyTable"
  },{
    "Effect": "Allow",
    "Action": [
      "logs:CreateLogGroup", "logs:CreateLogStream",
      "logs:PutLogEvents"
    ],
    "Resource": "arn:aws:logs:*:*:*"
  }]
}

Pattern 2: EC2 instance profile for reading Secrets Manager — Create a role, trust ec2.amazonaws.com, attach a policy with secretsmanager:GetSecretValue on the specific secret ARN. Attach the role as an instance profile when launching the EC2 instance.

Pattern 3: Developer read-only access to production — Create a role in the prod account, trust your dev account, attach ReadOnlyAccess managed policy. Developers assume the role with MFA required as a condition. No permanent prod access keys exist.

Troubleshooting Access Denied Errors

Access Denied errors in AWS are notoriously terse. Here is the systematic approach to diagnosing them:

Step 1: Identify the exact API call and principal. The CloudTrail log entry for the denied call contains the exact action (s3:PutObject), the exact resource ARN, and the principal (the IAM user, role, or assumed role session) that made the call. If CloudTrail is enabled, you can find this in the CloudTrail console or Athena.

Step 2: Check whether it is an identity-based policy issue or a resource-based policy issue. Some AWS services have resource policies (S3 bucket policies, KMS key policies, SQS queue policies) that independently control access. An explicit Deny in the resource policy will override an Allow in the identity policy.

Step 3: Use the IAM Policy Simulator. Navigate to IAM > Policy Simulator, select the principal, enter the action and resource, and simulate. The simulator shows exactly which policy statement is causing the deny.

Step 4: Check for SCPs (Service Control Policies). If your account is part of an AWS Organization, SCP denies at the organizational level override all IAM policies. Your account admin may have applied SCPs that restrict specific services or regions.

Common gotcha: S3 operations require permissions on both the bucket (arn:aws:s3:::bucket-name) and the objects (arn:aws:s3:::bucket-name/*). Granting only one and not the other is a common source of confusing Access Denied errors.

IAM Best Practices Checklist

Run through this checklist for every AWS account:

Frequently Asked Questions

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

An IAM user has permanent, long-term credentials (password, access keys) and is designed for human beings. An IAM role has no permanent credentials — it issues temporary credentials when assumed, valid for 15 minutes to 12 hours. Roles are the correct mechanism for applications, services, Lambda functions, EC2 instances, and cross-account access.

Can I give an EC2 instance admin access to AWS?

Technically yes, but you should never do this. Attaching AdministratorAccess to an EC2 instance role means that any code running on that instance — including any code injected by an attacker — has full admin access to your AWS account. Always use least-privilege roles with only the permissions the specific application needs.

How do I rotate IAM access keys?

Create a new access key, update all applications using the old key to use the new one, verify the applications work correctly, then deactivate and delete the old key. AWS recommends rotating access keys every 90 days. Better: eliminate long-term access keys entirely by using IAM roles and temporary credentials everywhere.

What is an IAM policy condition?

A condition is an optional clause in an IAM policy statement that adds constraints beyond just the action and resource. Conditions can restrict access based on source IP address, time of day, MFA status, requested region, resource tags, and many other attributes. Use conditions to enforce MFA for sensitive operations or restrict access to specific corporate IP ranges.

IAM is the foundation of AWS security. Get the skills.

Join professionals from Denver, NYC, Dallas, LA, and Chicago for two days of hands-on AI and tech training. $1,490. October 2026. Seats are limited.

Reserve Your Seat

Note: Information in this article reflects the state of the field as of early 2026. Technology evolves rapidly — verify specific version numbers, pricing, and service availability directly with vendors before making decisions.

BP

Bo Peng

AI Instructor & Founder, Precision AI Academy

Bo has trained 400+ professionals in applied AI across federal agencies and Fortune 500 companies. Former university instructor specializing in practical AI tools for non-programmers. Kaggle competitor and builder of production AI systems. He founded Precision AI Academy to bridge the gap between AI theory and real-world professional application.

Explore More Guides