AWS IAM Roles Explained: Trust Policy, Permissions & Examples
An IAM role is an AWS identity that grants temporary credentials instead of permanent access keys. You assign a role to a service, workload, or user, and AWS issues short-lived credentials automatically. Roles are the recommended way to grant access for EC2 instances, Lambda functions, ECS tasks, CI/CD pipelines, and cross-account workflows.
This guide explains what roles are, how their trust and permissions policies work together, and when to use a role instead of an IAM user or access key. By the end, you will understand the role assumption flow, know how to set up an EC2 instance profile, and have clear guidance on which identity type to choose for any workload.
Simple explanation
An IAM role is a set of permissions that is not tied to a specific person or permanent password. Instead of giving your application a username and password that never change, you give it a role. When the application needs access, AWS checks the role, issues a short-lived pass, and the pass expires on its own.
This matters because permanent credentials can leak. If an access key ends up in a Git repository, anyone who finds it has access until someone manually revokes it. With a role, the credentials expire in minutes to hours. Even if they leak, the window of exposure is small.
Analogy
A role is like a visitor badge at an office building. The badge grants access to specific floors and rooms, and it expires at the end of the day. Anyone authorized to visit can pick up the badge at the front desk, use it while they are in the building, and it stops working when they leave. Nobody needs to track down the badge and destroy it. It simply expires.
Why IAM roles matter
Permanent credentials are one of the most common sources of AWS security incidents. Long-lived access keys get committed to repositories, pasted into chat, or left in decommissioned servers. Roles eliminate this category of risk.
- No long-lived credentials in code. Applications never store permanent keys. The AWS SDK retrieves temporary credentials from the instance metadata service or the Lambda runtime automatically.
- Automatic rotation. AWS refreshes the temporary credentials before they expire. You do not need to build credential rotation logic.
- Reduced blast radius. If temporary credentials leak, they expire within minutes to hours. An attacker cannot use them indefinitely.
- Clean delegation. Roles let you grant access across AWS accounts or to external identity providers without sharing passwords or keys. Each side manages its own policies independently.
If you are building anything on AWS today, start with a role. The only time you should reach for access keys is when the workload runs outside AWS and cannot use OIDC federation. For broader context on how IAM fits into AWS, see the IAM overview.
How IAM roles work
Every IAM role has two policy documents that work together: a trust policy and one or more permissions policies. The trust policy controls who can use the role. The permissions policies control what the role can do.
Two policies, one role
Think of the trust policy as the lock on a door and the permissions policy as what is inside the room. The trust policy answers “who can open this door?” The permissions policy answers “once inside, what can you touch?” Both policies must be in place for a role to work.
Trust policy
The trust policy is a JSON document attached to the role that lists which principals are allowed to call sts:AssumeRole. A principal can be an AWS service, an IAM user, another AWS account, or an external identity provider.
Here is a trust policy that allows EC2 instances to assume the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}Common principal values:
“Service”: “ec2.amazonaws.com”for EC2 instances”Service”: “lambda.amazonaws.com”for Lambda functions”AWS”: “arn:aws:iam::123456789012:root”for all principals in another AWS account”Federated”: “token.actions.githubusercontent.com”for GitHub Actions via OIDC
You can add conditions to a trust policy to restrict assumption further. For example, you can require that a GitHub Actions workflow runs on a specific repository branch before it can assume the role.
Permissions policy
The permissions policy works the same way as a policy on an IAM user. It lists the AWS actions and resources the role holder can access. Here is a policy granting read access to a specific S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-data-bucket",
"arn:aws:s3:::my-app-data-bucket/*"
]
}
]
}You can attach AWS managed policies, customer managed policies, or write inline policies directly on the role. For guidance on choosing between these, see managed vs customer managed policies. For the JSON anatomy of a policy document, see IAM policy structure.
Temporary credentials and role assumption
When a principal assumes a role, AWS Security Token Service (STS) returns three values: a temporary access key ID, a secret access key, and a session token. These credentials expire after a configurable period (typically one hour, up to 12 hours via the role’s MaxSessionDuration setting).
The assumption flow works like this:
- A trusted principal (EC2, Lambda, a user, or an external identity) requests to assume the role.
- AWS checks the role’s trust policy to confirm the principal is allowed.
- STS issues temporary credentials scoped to the role’s permissions policies.
- The principal uses those credentials to make AWS API calls.
- The credentials expire automatically. No cleanup is needed.
For a deeper walkthrough of the AssumeRole API and cross-account patterns, see role assumption in AWS.
Instance profile vs role
EC2 instances cannot reference an IAM role directly. Instead, you create an instance profile, which is a container that holds exactly one role. You attach the instance profile to the EC2 instance at launch, and the instance metadata service (IMDS) vends temporary credentials for the role automatically.
When you create a role in the AWS Console and select “EC2” as the trusted entity, the console creates the instance profile for you. When using the CLI or Terraform, you must create the instance profile separately.
Lambda, ECS, and other services do not use instance profiles. They reference the role ARN directly in their configuration. The instance profile concept is specific to EC2.
Common use cases
EC2 instance profile
An EC2 instance assumes a role through its instance profile. Any application running on the instance can call AWS APIs without storing credentials. The AWS SDK reads temporary credentials from IMDS automatically.
Lambda execution role
Every Lambda function requires an execution role. The role grants the function permission to write logs to CloudWatch, read from DynamoDB, publish to SNS, or whatever the function needs. Lambda assumes the role on every invocation.
Cross-account access
A role in Account B can trust principals in Account A. Users or services in Account A call sts:AssumeRole to get temporary credentials for Account B. This avoids sharing IAM users or access keys between accounts. It is the standard pattern in multi-account setups.
GitHub Actions and OIDC federation
GitHub Actions can assume a role using OpenID Connect (OIDC) federation instead of storing long-lived access keys as repository secrets. The trust policy validates the OIDC token from GitHub and grants temporary credentials for the deployment workflow.
EKS with IAM Roles for Service Accounts (IRSA)
Pods running in EKS can assume IAM roles through IRSA. Each Kubernetes service account maps to an IAM role, so individual pods get only the permissions they need instead of sharing a single node-level role.
The pattern is always the same: a trusted entity assumes a role, gets temporary credentials, and uses them until they expire. Whether it is EC2, Lambda, GitHub Actions, or a Kubernetes pod, the flow is identical. Learn it once and you understand all of these use cases.
IAM roles vs IAM users vs access keys
This table helps you decide which identity type fits your situation. The short version: use roles for workloads, users for humans, and avoid access keys unless you have no alternative.
| IAM role | IAM user | Access keys | |
|---|---|---|---|
| Intended for | Services, workloads, cross-account access, federation | Individual humans needing console or CLI access | Programmatic access (attached to an IAM user) |
| Credential type | Temporary (STS) | Permanent password, optional access keys | Permanent key pair (access key ID + secret) |
| Credential lifetime | Minutes to 12 hours, auto-expires | Until manually rotated or deleted | Until manually rotated or deleted |
| Common use cases | EC2, Lambda, ECS, CI/CD, cross-account | Console login, local CLI usage | Legacy scripts, external systems without federation |
| Recommended | Yes, for all workloads | Yes, for human access with MFA | Avoid inside AWS; use only when roles are not possible |
| Key risk | Over-permissioned trust or permissions policies | Stale accounts, missing MFA | Leaked keys grant persistent access |
For a side-by-side walkthrough, see IAM users vs roles. For details on how access keys work and why they carry risk, see access keys explained.
Real example: EC2 reading from S3
This walkthrough creates a role that allows an EC2 instance to read objects from S3, then launches an instance with that role attached via an instance profile. All commands use the AWS CLI.
Step 1: Create the role with a trust policy
# Save the trust policy to a file
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EOF
# Create the role
aws iam create-role \
--role-name MyEC2S3ReadRole \
--assume-role-policy-document file://trust-policy.jsonStep 2: Attach a permissions policy
This attaches the AWS managed AmazonS3ReadOnlyAccess policy. In production, create a least-privilege custom policy that scopes access to specific buckets.
aws iam attach-role-policy \
--role-name MyEC2S3ReadRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccessStep 3: Create an instance profile and add the role
# Create the instance profile (required for EC2)
aws iam create-instance-profile \
--instance-profile-name MyEC2S3ReadProfile
# Add the role to the instance profile
aws iam add-role-to-instance-profile \
--instance-profile-name MyEC2S3ReadProfile \
--role-name MyEC2S3ReadRoleStep 4: Launch an EC2 instance with the profile
Replace ami-0abcdef1234567890 with a valid AMI ID for your region.
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.micro \
--iam-instance-profile Name=MyEC2S3ReadProfile \
--count 1Once the instance is running, any code on it can call S3 APIs without any access keys. The AWS SDK reads credentials from the instance metadata service automatically. The credentials refresh before they expire, so the application never needs to handle rotation.
The AmazonS3ReadOnlyAccess managed policy grants read access to all S3 buckets in the account. For production workloads, write a custom policy that restricts access to only the buckets and prefixes the application needs. See least privilege for guidance.
When to use a role
- Workloads running in AWS. EC2, Lambda, ECS, EKS, CodeBuild, Step Functions. Always use a role. Never embed access keys.
- Cross-account access. Create a role in the target account and let the source account assume it.
- CI/CD pipelines. Use OIDC federation with GitHub Actions, GitLab, or other providers instead of storing access keys as secrets.
- Third-party access. Grant vendors a role with scoped permissions and an external ID condition, rather than creating IAM users for them.
- Temporary privilege escalation. A developer assumes a role with elevated permissions for a specific task, then the session expires.
Only fall back to access keys when no role-based option exists, such as on-premises scripts that cannot use OIDC federation.
Common mistakes
- Over-permissioning roles. Attaching
AdministratorAccessto a role to “make things work” turns the role into a master key. Always scope permissions to exactly what the workload needs. Start with the principle of least privilege. - Overly broad trust policies. A trust policy with
"AWS": "*"lets any principal in any AWS account assume the role. Always restrict the principal to the specific service, account, or identity you intend to trust. - Confusing role and instance profile. A role cannot be attached to an EC2 instance directly. You must create an instance profile, add the role to it, and reference the profile at launch. Missing this step causes authentication errors that are hard to diagnose. See fixing IAM AccessDenied errors for common symptoms.
- Using IAM users with keys for AWS workloads. Creating IAM users with access keys for applications running on EC2 or in containers is unnecessary and dangerous. Roles provide the same access with temporary credentials and no risk of key leakage.
- Reusing one role across environments. A role used in development should not have access to production resources. Create separate roles per environment with permissions scoped to that environment’s resources.
- Ignoring session duration defaults. Roles issue credentials that expire after one hour by default. If your workload needs longer sessions, set a higher
MaxSessionDurationon the role (up to 12 hours) and request it at assumption time.
Summary
- An IAM role is an identity with no permanent credentials. It issues temporary credentials through STS when assumed.
- Every role has a trust policy (who can assume it) and permissions policies (what it can do).
- EC2 uses instance profiles to hold a role. Lambda, ECS, and other services reference the role ARN directly.
- Prefer roles over access keys for all workloads running inside AWS.
- Use OIDC federation to give CI/CD pipelines role-based access without storing long-lived keys.
- Restrict trust policies to specific principals. Scope permissions policies to the minimum required actions and resources.
Frequently asked questions
What is the difference between an IAM role and an IAM user?
An IAM user has permanent credentials such as a password or access keys. An IAM role has no permanent credentials. Instead, a trusted entity assumes the role and receives temporary credentials that expire automatically. Roles are recommended for workloads; users are for humans who need console or CLI access.
Do IAM roles have access keys?
No. IAM roles do not have their own access keys. When something assumes a role, AWS STS issues a set of temporary credentials (access key ID, secret access key, and session token) that expire after a configurable period, typically one hour.
Can a human assume an IAM role?
Yes. A human user can assume a role if the trust policy allows their IAM user or federated identity as a principal. This is common for cross-account access, privilege escalation workflows, and SSO setups.
What is an instance profile?
An instance profile is a container that holds exactly one IAM role and associates it with an EC2 instance. EC2 cannot reference a role directly. You create an instance profile, add the role to it, and attach the profile when you launch the instance.
When should I use a role instead of access keys?
Use a role whenever the workload runs inside AWS, including EC2, Lambda, ECS, EKS, and CodeBuild. Use a role with OIDC federation for external CI/CD systems like GitHub Actions. Only use access keys when no role-based option exists, such as on-premises scripts that cannot federate.