šŸŽ‰ DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

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.

DevOpsBoysMay 17, 20264 min read
Share:Tweet

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
bash
# 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-users

When 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.

bash
# 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-engineer

The user inherits all permissions from their groups.


IAM Policies

A policy is a JSON document with permissions. It has:

  • Effect: Allow or Deny
  • 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:

json
{
  "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:

json
{
  "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:

  1. You create a role with a trust policy (who can assume it) and permission policies (what they can do)
  2. An entity (EC2, Lambda, developer, other AWS account) "assumes" the role
  3. AWS returns temporary credentials (valid 1–12 hours)
  4. The entity uses those credentials until they expire

EC2 Instance Role (most common):

bash
# 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-profile

Now your EC2 instance code can call AWS APIs without hardcoded credentials:

python
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:

json
// Lambda can assume this role
{
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "lambda.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
json
// 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"
      }
    }
  }]
}
json
// 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

ConceptHas credentialsUsed byDuration
UserYes (permanent)HumansPermanent
GroupNoGroups usersN/A
RoleNo (temporary)Services, apps1–12 hours
PolicyNoAttached to users/rolesN/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.

Newsletter

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

Comments