AWS IAM Roles and Users: Create and Manage IAM Identities
AWS gives you three types of identity to control who and what can access your resources: IAM users for humans, IAM groups for managing users at scale, and IAM roles for services and cross-account access. Roles are the preferred choice for workloads because they use temporary credentials that expire automatically. Nothing to rotate, nothing to leak. This guide is a practical, CLI-focused walkthrough where you will create each identity type, attach policies, and learn the security patterns that keep your account safe.
Why this matters
IAM is the foundation of every AWS account. If your identity model is wrong, no amount of encryption or network controls will save you.
Long-lived credentials are the number-one source of AWS breaches. When an IAM user’s access key leaks into a Git repo, a log file, or a Slack message, anyone who finds it has permanent access until you revoke it. Roles avoid this entirely because their credentials expire in minutes or hours. See why long-lived access keys are dangerous for real-world examples.
Roles reduce blast radius. Temporary credentials are scoped to a single session. If they are intercepted, the window of exposure is short. Permanent access keys remain valid until someone remembers to rotate them.
Beginners get IAM wrong when they memorise commands before understanding the identity model. Creating an IAM user and generating access keys is the easiest path, but it is almost never the right one for workloads. Understanding why roles exist, not just how to create them, prevents habits that are painful to undo later.
Simple explanation
Think of your AWS account as a building. An IAM user is a person with a permanent badge. They keep it forever unless you take it away. An IAM group is a team roster: everyone on the “Developers” roster gets the same door access without you configuring each badge individually. An IAM role is a visitor pass that expires at the end of the day. A service like Lambda or EC2 picks it up, uses it, and the pass becomes worthless after a few hours.
For a deeper conceptual overview of how these fit together with policies and permission evaluation, see the IAM in AWS guide.
| Identity | What it represents | Long-term credentials? | Typical use case | When to avoid it |
|---|---|---|---|---|
| IAM user | A single person or service account | Yes (password and/or access keys) | Human admin who needs console access and cannot use SSO | Applications, EC2, Lambda, or any automated workload |
| IAM group | A collection of IAM users | No (groups have no credentials) | Assigning the same policies to all developers or all auditors at once | Granting service permissions (groups contain only users, not roles) |
| IAM role | A temporary identity assumed by a trusted entity | No (uses STS temporary credentials) | EC2 instances, Lambda functions, cross-account access, CI/CD pipelines | Rarely. Roles are the default best practice for almost everything except direct human console access |
How IAM identities work
Authentication vs authorisation
Think of airport security. Authentication is the passport check: proving you are who you say you are. Authorisation is the boarding pass: proving you are allowed on this flight. You need both to get on the plane, and AWS works the same way.
Authentication uses a username and password, or an access key pair. Authorisation is handled by IAM policies. Every API call in AWS goes through both checks: “is this caller who they claim to be?” and “does the attached policy allow this action?”
Users, groups, and policies
An IAM user can have policies attached directly, but that does not scale. Instead, you
create groups (like Developers or Auditors), attach policies to the
group, and add users to it. When someone changes teams, you move them between groups instead
of rewriting their permissions.
Never attach policies directly to individual users if you can avoid it. Policies on groups are easier to audit, easier to change, and prevent the “snowflake user” problem where every person has a slightly different set of permissions that nobody fully understands. For a detailed look at policy types, see managed vs customer managed policies.
Trust policy vs permissions policy
A trust policy is like the bouncer at a door: it checks your ID and decides whether you are allowed in. A permissions policy is the menu once you are inside: it lists exactly what you can order. Without the bouncer (trust policy), nobody can enter. Without the menu (permissions policy), there is nothing to do once you get in.
Every IAM role has two kinds of policy. The trust policy says who can assume the role, for example the Lambda service or a specific account ID. The permissions policy says what the role can do once assumed, for example reading objects from an S3 bucket. Mixing these up is one of the most common beginner mistakes. For a deep dive, see IAM roles explained.
STS and temporary credentials
STS credentials work like a hotel key card. When you check in, the front desk programs a card that opens your room for the length of your stay. After checkout, the card stops working. You never get a copy of the master key, and if you lose the card, someone who finds it has only hours before it becomes useless plastic.
When a service assumes a role, AWS Security Token Service (STS) returns a temporary access key, secret key, and session token. These credentials expire automatically, typically after one hour (though the duration is configurable). Because they are short-lived, there is nothing to rotate and nothing that stays valid if leaked. See AWS STS and temporary credentials for the full picture.
Why EC2 needs an instance profile
Unlike Lambda, which accepts a role ARN directly, EC2 cannot attach a role on its own. You must create an instance profile (a container that holds exactly one role) and attach the profile to the instance. The EC2 metadata service then delivers the temporary credentials to code running on the instance. Forgetting this step is one of the most common “my EC2 has no permissions” debugging sessions.
When to use this
Here are the most common scenarios and which identity to reach for:
Human admin needs console and CLI access. Create an IAM user (or, in multi-account setups, use IAM Identity Center). Add the user to a group with appropriate permissions. Enable MFA.
Application running on EC2. Create an IAM role with EC2 as the trusted principal. Create an instance profile, add the role, and attach the profile to the instance. The application retrieves temporary credentials from the metadata service automatically, with no access keys needed.
Lambda function needs S3 access. Create an IAM role with Lambda as the trusted principal. Attach a permissions policy that grants the specific S3 actions your function needs. Assign the role when you create or update the function.
Cross-account read access. Create a role in the target account with a trust policy that allows the source account to assume it. Use an
ExternalIdcondition for third-party access. See role assumption in AWS for the full pattern.Legacy service account that cannot assume a role. Some older tools or on-premises scripts genuinely require static credentials. In that case, create an IAM user with only programmatic access, scope its policy tightly, and rotate keys on a schedule. This should be the exception, not the default.
The modern best practice is clear: use roles for workloads, use groups to manage human users, and create individual IAM users only when SSO is not an option. If you are starting a new project, default to roles for everything and work backwards from there.
Before you start
You need the iam:CreateUser, iam:CreateRole, and related
permissions to manage IAM identities. If you are the account root user or have administrator
access, you already have these.
Do not use the root user for day-to-day IAM management. Create a dedicated admin role instead. The root user should only be used for tasks that specifically require it, like changing account-level settings or recovering a locked-out admin.
All examples below use the AWS CLI. If you have not set it up yet, run
aws configure with the credentials of an IAM user or role that has IAM
admin permissions. For a broader reference of IAM CLI commands, see
managing IAM with the AWS CLI.
Creating and managing IAM users
Create an IAM user when a human needs direct AWS access and your organisation does not use IAM Identity Center (SSO). Each user gets their own credentials and can be added to groups for permission management.
Create a user with console access
# Create the user
aws iam create-user --user-name bob
# Give them a console password (they will be forced to change it on first login)
aws iam create-login-profile \
--user-name bob \
--password "InitialPassword123!" \
--password-reset-requiredCreate programmatic access keys
Only do this if the user genuinely needs CLI or API access with long-term credentials. For workloads, use a role instead.
# Generate an access key pair
aws iam create-access-key --user-name bobSave the SecretAccessKey immediately. It is shown exactly once. If you lose
it, you must delete the key pair and create a new one. Store it in a password manager,
never in plaintext files or chat messages.
List and inspect users
# List all users in the account with key details
aws iam list-users \
--query 'Users[*].{Name:UserName,Created:CreateDate,PasswordLastUsed:PasswordLastUsed}' \
--output table
# Get details about a specific user
aws iam get-user --user-name bobDelete a user safely
You cannot delete an IAM user that still has resources attached. Remove everything first, in this order:
# 1. Delete the console login profile
aws iam delete-login-profile --user-name bob
# 2. Delete all access keys
aws iam list-access-keys --user-name bob
aws iam delete-access-key --user-name bob --access-key-id AKIAXXXXXXXXXXXXXXXX
# 3. Detach all managed policies
aws iam list-attached-user-policies --user-name bob
aws iam detach-user-policy --user-name bob \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# 4. Delete all inline policies
aws iam list-user-policies --user-name bob
aws iam delete-user-policy --user-name bob --policy-name SomeInlinePolicy
# 5. Remove from all groups
aws iam list-groups-for-user --user-name bob
aws iam remove-user-from-group --user-name bob --group-name Developers
# 6. Now delete the user
aws iam delete-user --user-name bobIf you skip any of these steps, the delete-user command will fail with a
DeleteConflict error. AWS forces you to clean up dependencies first so you
do not accidentally leave orphaned access keys or dangling policy attachments.
Creating and managing groups
Groups let you assign permissions once and have every member inherit them automatically. Instead of attaching policies to each user individually, attach them to the group and manage team membership.
# Create a group
aws iam create-group --group-name Developers
# Attach a managed policy to the group
aws iam attach-group-policy \
--group-name Developers \
--policy-arn arn:aws:iam::aws:policy/PowerUserAccess
# Add a user to the group
aws iam add-user-to-group --user-name bob --group-name Developers
# List which groups a user belongs to
aws iam list-groups-for-user --user-name bob
# List all users in a group
aws iam get-group --group-name Developers \
--query 'Users[*].UserName' --output text
# Remove a user from a group
aws iam remove-user-from-group --user-name bob --group-name DevelopersStart with broad groups that match your team structure (Developers,
Auditors, Admins) and refine permissions over time using
the principle of least privilege.
It is much easier to tighten permissions on a group than to track down and update
policies on twenty individual users.
Creating and managing roles
Roles are the backbone of secure AWS access for services, applications, and cross-account workflows. Every role needs a trust policy (who can assume it) and at least one permissions policy (what it can do).
Role for a Lambda function
This creates a role that the Lambda service can assume, then attaches policies granting CloudWatch Logs and S3 read access.
# Create the role with a trust policy allowing Lambda to assume it
aws iam create-role \
--role-name my-lambda-execution-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach the basic Lambda execution policy (CloudWatch Logs access)
aws iam attach-role-policy \
--role-name my-lambda-execution-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Add S3 read access
aws iam attach-role-policy \
--role-name my-lambda-execution-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccessRole for an EC2 instance
EC2 requires an extra step: you must create an instance profile to deliver the role’s credentials to the instance. Without the instance profile, the role exists but EC2 cannot use it.
# Create the role with a trust policy allowing EC2 to assume it
aws iam create-role \
--role-name my-ec2-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach a policy granting SSM access (Session Manager without SSH keys)
aws iam attach-role-policy \
--role-name my-ec2-role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# Create the instance profile and add the role to it
aws iam create-instance-profile --instance-profile-name my-ec2-profile
aws iam add-role-to-instance-profile \
--instance-profile-name my-ec2-profile \
--role-name my-ec2-role
# When launching an instance, specify the profile:
# aws ec2 run-instances --instance-profile Name=my-ec2-profile ...If you create the role but forget the instance profile, your EC2 instance will launch with no IAM credentials at all. The AWS console creates the instance profile automatically when you assign a role, but the CLI requires you to do it manually. This catches a lot of beginners off guard.
Cross-account role
This role lives in Account B and allows Account A to assume it. The ExternalId
condition prevents the confused deputy problem when granting access to third
parties. For the full cross-account pattern, see
role assumption in AWS.
The confused deputy problem happens when a third-party service uses its
own permissions to access your resources on behalf of someone else. The
ExternalId acts like a shared secret between you and the third party, so
AWS can verify the request is actually coming from the expected caller, not a different
customer of the same service.
# In Account B: create a role that Account A can assume
aws iam create-role \
--role-name CrossAccountReadRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-12345"
}
}
}]
}'
# From Account A: assume the role and get temporary credentials
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccountReadRole \
--role-session-name cross-account-session \
--external-id unique-external-id-12345Creating custom policies
AWS managed policies are convenient but often grant more permissions than you need. Write a custom policy when you want least-privilege access: only the specific actions on the specific resources your workload requires. For details on how the JSON structure works, see IAM policy structure.
A managed policy like AmazonS3FullAccess is like giving someone a master key
to every room in the building. A custom policy is like giving them a key that only opens
the storage closet on the third floor. If that key gets stolen, the damage is limited to
one closet instead of the entire building.
The example below grants an application read/write access to a single S3 bucket, restricted
to the uploads/ prefix. This is far tighter than attaching
AmazonS3FullAccess, which would grant access to every bucket in the account.
# Write the policy document to a file
cat > my-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-app-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["uploads/*"]
}
}
}
]
}
EOF
# Create the policy in IAM
aws iam create-policy \
--policy-name MyAppUploadPolicy \
--policy-document file://my-policy.json \
--description "Read/write access to my-app-bucket uploads prefix only"
# Attach it to a role
aws iam attach-role-policy \
--role-name my-lambda-execution-role \
--policy-arn arn:aws:iam::123456789012:policy/MyAppUploadPolicyYou can also add conditions to policies for even finer control, such as restricting access by source IP or requiring MFA. See IAM policy conditions for examples.
Auditing IAM
IAM identities accumulate like clutter in a garage. People join the team, get permissions, and leave. Services get launched with broad policies “just for now” and nobody circles back. Regular audits catch stale users, unused permissions, and forgotten access keys before they become security incidents.
# Generate the credential report (CSV listing all users, key ages, MFA status)
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 --decode
# List all managed policies attached to a role
aws iam list-attached-role-policies --role-name my-lambda-execution-role
# List all inline policies on a user
aws iam list-user-policies --user-name bob
# Check when services were last accessed by a role (helps find unused permissions)
aws iam generate-service-last-accessed-details \
--arn arn:aws:iam::123456789012:role/my-lambda-execution-role
# Retrieve the results (wait a few seconds after generating)
aws iam get-service-last-accessed-details \
--job-id <job-id-from-above> \
--query 'ServicesLastAccessed[?TotalAuthenticatedEntities > `0`].{Service:ServiceName,LastAuth:LastAuthenticated}' \
--output tableUse the service-last-accessed data to identify permissions your roles no longer need, then tighten their policies. This is how you apply least privilege in practice, not just in theory. A role that has not used S3 in 90 days probably does not need S3 permissions.
Common beginner mistakes
Never use the root user for daily work. The root user has unrestricted access to everything in the account, including billing, account closure, and support plan changes. Enable MFA on root, create a separate admin role for day-to-day tasks, and treat the root credentials like a fire extinguisher: locked away, clearly labelled, and only touched in emergencies.
Creating access keys for workloads that should use roles. If your code runs on EC2, Lambda, ECS, or any other AWS service, it can assume a role and get temporary credentials automatically. Generating an IAM user and pasting access keys into a config file creates a permanent credential that can leak. Use a role instead.
Mixing up trust policies and permissions policies. The trust policy controls who can assume the role. The permissions policy controls what the role can do. If your Lambda function cannot assume its role, the trust policy is wrong. If it can assume the role but gets “Access Denied” on S3, the permissions policy is wrong.
Forgetting the instance profile step for EC2. Creating an IAM role alone is not enough for EC2. You must also create an instance profile, add the role to it, and attach the profile to the instance. Skip this step and the instance will have no credentials at all.
Attaching overly broad managed policies and never tightening them. Starting with
AdministratorAccessorAmazonS3FullAccessto “just get it working” is fine during development, but leaving those policies in production violates least privilege. Use service-last-accessed data to identify what you actually need, then replace the broad policy with a scoped one.Creating one-off inline policies everywhere. Inline policies are embedded in a single user, group, or role and cannot be reused or audited centrally. Prefer customer managed policies that you can attach to multiple identities and version over time.
Forgetting to save the secret access key. When you create an access key, the secret is shown exactly once. If you lose it, you must delete the key pair and create a new one. Save it in a password manager immediately.
Not attaching any permissions policy to a role. A freshly created role with no permissions policy can be assumed but cannot do anything useful. Always attach at least one policy before assigning the role to a service.
IAM users vs roles
An IAM user is like a house key cut on metal. It works forever, anyone who finds it can walk in, and you have to change the locks to revoke it. An IAM role is like a one-time entry code that the alarm company generates when you call. It works for one visit and then deactivates itself.
| IAM user | IAM role | |
|---|---|---|
| Credentials | Permanent (password, access keys) | Temporary (STS tokens, expire automatically) |
| Intended for | Humans who need direct console/CLI access | Services, applications, cross-account access |
| Rotation | Manual (you must rotate keys yourself) | Automatic (STS handles it) |
| Risk if leaked | High (valid until revoked) | Low (expires in minutes to hours) |
Use a user when:
- A human needs console access and your organisation does not use SSO
- An external tool requires static credentials and genuinely cannot assume a role
Use a role when:
- An AWS service (EC2, Lambda, ECS, CodeBuild) needs to call other AWS APIs
- You need cross-account access
- A CI/CD pipeline runs in AWS and needs deployment permissions
- You want to grant time-limited elevated access to a human (role assumption)
For a deeper comparison with more examples and security tradeoffs, see the dedicated IAM users vs roles guide.
Frequently asked questions
What is the difference between an IAM user and an IAM role?
An IAM user is a permanent identity with long-lived credentials (a password or access keys). An IAM role has no permanent credentials. It issues short-lived tokens through AWS STS when something assumes it. Users are for humans who need console or CLI access. Roles are for services, applications, and cross-account access.
When should I use an IAM role instead of an IAM user?
Use a role whenever the caller is an AWS service (EC2, Lambda, ECS), a CI/CD pipeline, or an entity in another AWS account. Roles issue temporary credentials that expire automatically, so there is nothing to rotate or leak. Only use IAM users for humans who cannot use IAM Identity Center (SSO).
What is the difference between a trust policy and a permissions policy?
A trust policy controls who can assume the role. It answers "who is allowed to use this identity?" A permissions policy controls what the role can do after it is assumed. It answers "what AWS actions are allowed?" Every role needs both: a trust policy to allow assumption and at least one permissions policy to grant access to resources.
Why does EC2 need an instance profile?
EC2 does not attach IAM roles directly. Instead, you create an instance profile, add the role to it, and attach the profile to the instance. The instance profile acts as a container that delivers the role credentials to code running on the instance via the instance metadata service. Lambda and ECS accept role ARNs directly, but EC2 requires this extra step.
How do I rotate or remove IAM access keys safely?
First, create a new access key for the user. Update every application and CLI profile that uses the old key. Verify the new key works. Then deactivate the old key and wait a few days before deleting it. Never delete an old key before confirming the new one works everywhere. Use the IAM credential report to find stale keys across your account.