AWS IAM deep dive: How roles work
Roles are a superior alternative to IAM user access keys
IAM roles are a way to use temporary credentials to access AWS APIs. Using them offers a more secure and process-based access to secrets. There are many use-cases for roles, and as a rule of thumb, they are superior to embedding IAM user credentials.
A credential in AWS is a pair of access key id and a secret access key. AWS APIs require them to sign the request, and everything that accesses resources in an account goes through the process. The AWS CLI and the SDKs make this process transparent, but it's there nonetheless.
Credentials
Let's see how these temporary credentials work!
IAM users can have access keys on their own, and these are long-lived credentials. On the Console, these are listed under the Security Credentials tab.
A key is active as long as it's in the Active state and it's not deleted. And it provides the same level of access to the account as the user.
The secret access key is visible only when you generate the key. On the Console, it is shown in the dialog:
Because these keys are permanent, there is nothing that prevents a malicious party to use them indefinitely. It's a real risk that someone generates a key, puts it into a system somewhere then forgets about it completely. And as there is no connection between the key and where it is embedded, a security audit can only see that the key is active.
A role provides the same access key and secret access key but with a timestamp attached. After a set amount of time, the credentials become unusable and whoever wants to use them must request new ones. This is done using the assume role action.
On the role's page, you can see the Maximum session duration. This can go up to 24 hours.
Who can use the role?
Assuming a role requires permissions. First, the role has to define who is trusted (using the trust policy), then the identity (user or role)
needs the sts:AssumeRole
permission. Because of this, using a role requires an identity inside AWS. This can be a user, another role, even in a different
account, or a service that supports it. But you can't use a role directly with a web-based login on your website, you need to use Cognito or SAML for that.
With roles, issuing the credentials are part of a process. When a security auditor inspects the account, he can see which roles are used and by whom.
How to use roles
Roles have wide tooling support as they are an integral part of how systems work. The Console allows easy intra- and cross-account access with a one-click switch to previous roles. The AWS CLI similarly supports them via profiles, and the SDKs have built-in classes too.
Management Console
On the Console, you can use the link on the IAM page to switch to a role, or you can input the account ID and the role's name manually. Both works for both same- and cross-account.
When you use the Console via a role, you'll see the role's name and the account ID on the top right:
Using a role with the Console requires more permissions than the CLI. For example, even if you have an s3:GetObject
permission to a specific object,
but unless you also have the s3:ListAllMyBuckets
you won't be able to select the bucket.
AWS CLI profile
The AWS CLI supports roles via profiles. Hardcoding the role's ARN is useful when you use it regularly and want a quick way to switch to it.
[profile marketingadmin]
role_arn = arn:aws:iam::123456789012:role/marketingadminrole
source_profile = user1
SDK
The Javascript SDK also supports transparently chaining credentials using roles. The ChainableTemporaryCredentials
provides a credentials provider that
assumes a role in the background and other services can use it.
To use S3 with a specific role, use:
const credentials = new AWS.ChainableTemporaryCredentials({
params: {RoleArn: roleArn}
});
const s3 = new AWS.S3({credentials});
As roles expire, timing requires extra care. The SDK supports two parameters to control it.
The first is how long the role is valid when it is assumed. This is the DurationSeconds
parameter:
const credentials = new AWS.ChainableTemporaryCredentials({
params: {
RoleArn: roleArn,
DurationSeconds: ...,
}
});
Note that it does not guarantee that when the credentials are used they are still valid for that amount of time. If the code makes 2 calls 15 minutes apart then the second won't refresh the credentials so it will be available for a shorter duration.
To control the minimum amount of time the credentials are valid, use the expiryWindow
parameter. When the credentials provider sees that the cached
credentials expire within the window it refreshes them automatically.
const credentials = new AWS.ChainableTemporaryCredentials({
params: {RoleArn: roleArn}
});
credentials.expiryWindow = 15 * 60; // 15 minutes
const s3 = new AWS.S3({credentials});
This setup makes sure the credentials are valid for at least 15 minutes.
CLI usage
The AWS CLI provides the assume-role command to get a set of temporary credentials. It needs the role's ARN and a session name.
aws sts assume-role --role-arn <role arn> --role-session-name test_session
{
"Credentials": {
"AccessKeyId": "ASIA...",
"SecretAccessKey": "6mu...",
"SessionToken": "IQoJb3JpZ2luX2V...",
"Expiration": "2020-09-30T08:14:44+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROA...:test_session",
"Arn": "arn:aws:sts::<accountid>:assumed-role/test_role/test_session"
}
}
The AccessKeyId
, the SecretAccessKey
, and the SessionToken
form the credentials that you need to use the role. The easiest way is to use
environment variables:
AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=... aws sts get-caller-identity
You can also use AWSudo to do this automatically:
npx awsudo <role arn> aws sts get-caller-identity
{
"UserId": "AROA...:RoleSession",
"Account": "<accountid>",
"Arn": "arn:aws:sts::<accountid>:assumed-role/test_role/RoleSession"
}
IAM policy types for roles
Two types of policies are required for roles. The first one is the trust policy that defines who can assume that role. This controls access to the role's permissions.
The other one is the permissions policy that controls what the role can do.
Trust policy
The trust policy is a resource-based policy attached to the role. It defines who can assume the role. There are two ways of how a role can trust an identity.
First, it can define the identity using the Principal element. Since it hard-codes a user or a role there are no other policies needed.
Alternatively, it can trust an account, in which case the identity also needs permissions to assume the role. This is the more common scenario.
A role can also trust a service in AWS. This is needed when you configure a service to use a specific role. Since, usually, even AWS services can not access resources in your account you can give them access this way.
This is the case in several cases, such as configuring an execution role for a Lambda function, giving access to CloudTrail to push events to CloudWatch Logs, and the EC2 instance profile.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Permissions policy
The permissions policy is an identity-based policy that defines what the role can do. This lists all the permissions that users can do when they assume the role. This works the same as how policies attached to IAM users work.
For example, this policy gives access to the objects in a specific bucket:
Conclusion
IAM roles allow process-based handling of credentials instead of hardcoding long-lived secrets into systems. They are an integral part of an AWS account, and they are usually a superior alternative to users.
Tools provide first-class support to roles. The Console allows one-click switching even between accounts, the AWS CLI allows profiles to transparently assume and use roles.
Who gets to assume a role depends on the trust policy attached to it. This provides the primary access control to roles. Make sure it's as locked down as possible as temporary credentials won't help when an attacker can just request new ones.