An IAM role grants temporary credentials, acting like a borrowed identity, whereas an IAM user has persistent credentials, like a permanent employee.
Let’s see this in action. Imagine a web application running on an EC2 instance that needs to access an S3 bucket.
# On the EC2 instance, using the AWS CLI
aws s3 ls s3://my-secure-bucket
If the EC2 instance has an IAM role attached, the AWS CLI automatically picks up temporary credentials associated with that role. The output would be a list of objects in my-secure-bucket.
# Example of temporary credentials obtained by the EC2 instance
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"SessionToken": "...",
"Expiration": "2023-10-27T10:00:00Z"
}
If you tried to do this with an IAM user’s static credentials (access key ID and secret access key) directly configured on the EC2 instance, it would work, but it’s a less secure and less flexible approach.
The core problem IAM roles solve is the secure delegation of permissions without hardcoding long-lived credentials. For IAM users, you create an identity, assign policies, and then generate access keys. These keys, if compromised, provide ongoing access until they are manually rotated or deleted. For roles, you define a trust policy (who or what can assume the role) and a permissions policy (what the role can do). When an entity assumes a role, AWS issues temporary security credentials that expire. This drastically reduces the attack surface.
Here’s how it breaks down internally:
-
IAM User:
- Identity: A long-lived principal with a username and password (for console access) and associated access keys (for programmatic access).
- Permissions: Assigned directly via IAM policies (managed or inline).
- Credentials: Static access keys (Access Key ID and Secret Access Key) that don’t expire unless manually rotated.
-
IAM Role:
- Identity: Not a principal that logs in directly. It’s an identity that can be assumed by trusted entities.
- Trust Policy: Defines which AWS services (like EC2, Lambda) or AWS accounts can assume this role.
- Permissions Policy: Defines the actions the role is allowed to perform.
- Assumption: When a trusted entity (e.g., an EC2 instance, a Lambda function) assumes the role, AWS STS (Security Token Service) generates temporary security credentials. These credentials consist of an Access Key ID, a Secret Access Key, and a Session Token.
- Credentials: Temporary and time-limited (default is 1 hour, max 36 hours for EC2, max 12 hours for others). The service or application periodically refreshes these credentials before they expire.
The primary lever you control with IAM roles is the trust policy. This is a JSON document that specifies who can sts:AssumeRole. For an EC2 instance, the trust policy typically looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
This allows the ec2.amazonaws.com service principal to assume this role, meaning any EC2 instance can be granted these permissions by attaching this role.
You also define the permissions policy for the role. For example, to allow read-only access to a specific S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::my-secure-bucket",
"arn:aws:s3:::my-secure-bucket/*"
]
}
]
}
The key difference in how credentials are used is crucial. When you configure the AWS CLI with static user credentials, you typically run aws configure and provide the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. These values are stored locally and used directly. For roles, especially on AWS services, the SDKs or CLIs detect the presence of an attached role and automatically call the STS AssumeRole API to obtain temporary credentials. These temporary credentials are then used for subsequent API calls. This means you never have to manage long-lived secret keys for applications running on AWS infrastructure.
Most people think of roles as just a way to grant permissions to services. However, they’re also incredibly powerful for cross-account access. If you want to grant an IAM user in Account A permission to access resources in Account B, you can create a role in Account B with the desired permissions and a trust policy that allows principals from Account A (either specific users or the entire account) to assume it. The user in Account A then uses sts:AssumeRole to get temporary credentials for the role in Account B, effectively borrowing its permissions.
The next step is understanding how to create and manage these trust and permissions policies programmatically using the AWS SDKs.