What is AWS IAM? Roles, Policies, Users, and Groups Explained Simply
IAM is how AWS decides who can do what. Here's a plain-English explanation of users, groups, roles, and policies ā with real examples of how they're used together.
Every AWS action ā launching an EC2 instance, reading an S3 bucket, calling a Lambda ā goes through IAM first. IAM decides: "Is this entity allowed to do this thing?"
Here's how it works.
The Four Core Concepts
IAM User ā A person (or application) with long-term credentials (username/password, access keys).
IAM Group ā A collection of users. Assign policies to the group instead of to each user individually.
IAM Role ā An identity that can be assumed temporarily. No permanent credentials. Used by AWS services, EC2 instances, Lambda functions, and cross-account access.
IAM Policy ā A JSON document that defines what actions are allowed or denied, on which resources.
IAM Users
An IAM user gets credentials that don't expire:
- Console password ā for AWS Management Console
- Access key + secret key ā for CLI and API calls
# Create a user
aws iam create-user --user-name devops-engineer
# Create access keys for the user
aws iam create-access-key --user-name devops-engineer
# Returns: AccessKeyId + SecretAccessKey (save these ā shown only once)
# List users
aws iam list-usersWhen to use IAM Users:
- Human engineers who need AWS Console access
- Legacy applications that can't use roles (use sparingly)
When NOT to use IAM Users:
- EC2 instances (use IAM Roles instead)
- Lambda functions (use IAM Roles)
- EKS pods (use IRSA ā IAM Roles for Service Accounts)
- CI/CD pipelines (use OIDC + IAM Role where possible)
IAM Groups
Groups let you manage multiple users' permissions together.
# Create a group
aws iam create-group --group-name DevOpsEngineers
# Attach a policy to the group
aws iam attach-group-policy \
--group-name DevOpsEngineers \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
# Add user to group
aws iam add-user-to-group \
--group-name DevOpsEngineers \
--user-name devops-engineerThe user inherits all permissions from their groups.
IAM Policies
A policy is a JSON document with permissions. It has:
- Effect:
AlloworDeny - Action: What API calls are allowed (e.g.,
s3:GetObject,ec2:*) - Resource: Which specific resources (e.g., a specific S3 bucket, or
*for all)
Example ā allow reading a specific S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-company-logs",
"arn:aws:s3:::my-company-logs/*"
]
}
]
}Example ā deny deleting anything:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"ec2:TerminateInstances",
"rds:DeleteDBInstance",
"s3:DeleteBucket"
],
"Resource": "*"
}
]
}Key rule: Deny always wins over Allow. Even if another policy allows an action, an explicit Deny blocks it.
Policy types:
- AWS Managed Policies ā Pre-built by AWS (e.g.,
AmazonS3FullAccess,AdministratorAccess) - Customer Managed Policies ā You create and manage
- Inline Policies ā Attached directly to a user/group/role (avoid these ā hard to manage)
IAM Roles
A role is the most powerful and most-used IAM concept in modern AWS.
How a role works:
- You create a role with a trust policy (who can assume it) and permission policies (what they can do)
- An entity (EC2, Lambda, developer, other AWS account) "assumes" the role
- AWS returns temporary credentials (valid 1ā12 hours)
- The entity uses those credentials until they expire
EC2 Instance Role (most common):
# Create a role that EC2 can assume
aws iam create-role \
--role-name ec2-app-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach permissions to the role
aws iam attach-role-policy \
--role-name ec2-app-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Create instance profile and attach role
aws iam create-instance-profile --instance-profile-name ec2-app-profile
aws iam add-role-to-instance-profile \
--instance-profile-name ec2-app-profile \
--role-name ec2-app-role
# Attach to EC2 instance at launch or modify
aws ec2 associate-iam-instance-profile \
--instance-id i-1234567890abcdef0 \
--iam-instance-profile Name=ec2-app-profileNow your EC2 instance code can call AWS APIs without hardcoded credentials:
import boto3
# Credentials automatically from instance role ā no keys needed
s3 = boto3.client('s3')
s3.get_object(Bucket='my-bucket', Key='config.json')Trust Policies ā Who Can Assume the Role
The trust policy defines who can assume a role:
// Lambda can assume this role
{
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}// Another AWS account can assume this role (cross-account)
{
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::987654321:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-123"
}
}
}]
}// GitHub Actions CI/CD can assume this role (OIDC)
{
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"
}
}
}]
}Least Privilege ā The Golden Rule
Never give more permissions than needed.
ā Bad: Attach AdministratorAccess to EC2 instances
ā
Good: Create a role with only the specific S3 bucket and specific actions needed
ā Bad: Give developers AdministratorAccess
ā
Good: Give developers access to specific services, deny delete actions
ā Bad: Use long-term access keys in CI/CD
ā
Good: Use OIDC to assume a role with minimal CI/CD permissions
Quick Reference
| Concept | Has credentials | Used by | Duration |
|---|---|---|---|
| User | Yes (permanent) | Humans | Permanent |
| Group | No | Groups users | N/A |
| Role | No (temporary) | Services, apps | 1ā12 hours |
| Policy | No | Attached to users/roles | N/A |
For AWS IAM, security, and SAA-C03 exam prep, KodeKloud has hands-on AWS labs that cover IAM deep dives, cross-account access, and IRSA for Kubernetes.
Today I Fixed
Short real fixes from production ā posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam ā just practical engineering content.
Related Articles
AWS IAM Permission Denied Errors ā How to Fix Every Variant (2026)
Getting 'Access Denied' or 'is not authorized to perform' errors in AWS? Here's how to diagnose and fix every IAM permission issue ā EC2, EKS, Lambda, S3, and CLI.
AWS IRSA Permission Denied in Kubernetes ā Fix
Your Kubernetes pod can't access AWS services even though IRSA is configured. Here's every reason IRSA fails and exactly how to debug and fix each one.
AWS Secrets Manager vs HashiCorp Vault vs External Secrets Operator 2026
Choosing between AWS Secrets Manager, HashiCorp Vault, and External Secrets Operator for Kubernetes? Here's a practical breakdown of when to use each and what the trade-offs actually are.