AWS IAM Role Assumtion Pecularity

Hi all. I am staring to write a series of posts about AWS. This article is the first one in the series. The goal is not to provide a deep dive into the AWS services, but to share some interesting and useful tips about AWS, which I have learned on my projects.

Today I will outline an interesting peculiarity of AWS IAM role assumption. I think it is important to understand for building better permission and trust policies for your IAM roles. I identified this interesting behavior while working on a project where I needed to restrict which roles can assume a particular role inside the same account.

The problem

Let’s say there is an IAM role in the account, the role name is a RoleA. This role has a permission to assume other roles, but only those which are placed in the specific ARN path, e.g. allowed-roles-path. The RoleA ARN is the following: arn:aws:iam::123456789012:role/RoleA. Its permissions policy for assuming roles is the following:

 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4    {
 5      "Effect": "Allow",
 6      "Action": "sts:AssumeRole",
 7      "Resource": "arn:aws:iam::123456789012:role/allowed-roles-path/*"
 8    }
 9  ]
10}

Question: What kind of behavior is expected here?

From the first glance it looks like that the RoleA can assume only roles which are placed in the allowed-roles-path. But this is NOT the case. Behavior depends on the trust policy of the role which is being assumed.

Let’s create four more roles in the account: RoleB, RoleC, RoleD, and RoleE.

  1. RoleB ARN: arn:aws:iam::123456789012:role/RoleB and its trust policy allows to be assumed by any role in the account.
 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4        {
 5          "Effect": "Allow",
 6          "Principal": {
 7            "AWS": "arn:aws:iam::123456789012:root"
 8            },
 9            "Action": "sts:AssumeRole"
10        }
11    ]
12}
  1. RoleC ARN: arn:aws:iam::123456789012:role/RoleC and its trust policy allows to be assumed only by the RoleA.
 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4        {
 5          "Effect": "Allow",
 6          "Principal": {
 7            "AWS": "arn:aws:iam::123456789012:role/RoleA"
 8            },
 9            "Action": "sts:AssumeRole"
10        }
11    ]
12}
  1. RoleD ARN: arn:aws:iam::123456789012:role/allowed-roles-path/RoleD and its trust policy allows to be assumed by any role in the account.
 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4        {
 5          "Effect": "Allow",
 6          "Principal": {
 7            "AWS": "arn:aws:iam::123456789012:root"
 8            },
 9            "Action": "sts:AssumeRole"
10        }
11    ]
12}
  1. RoleE ARN: arn:aws:iam::123456789012:role/allowed-roles-path/RoleE and its trust policy allows to be assumed only by the RoleA.
 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4        {
 5          "Effect": "Allow",
 6          "Principal": {
 7            "AWS": "arn:aws:iam::123456789012:role/RoleA"
 8            },
 9            "Action": "sts:AssumeRole"
10        }
11    ]
12}

Expected behavior:

  • The RoleA can assume the RoleD and the RoleE only, because they are placed in the allowed-roles-path.

Actual behavior:

  • The RoleA can assume the roles RoleC, RoleD and the RoleE.

Surprised? I was surprised too when I found this out. I did not expect to see the RoleC in the list of assumable roles by the RoleA, because it is placed in the root path, not in the allowed-roles-path.

Let’s analyze case by case how evaluation of policies works.

  1. RoleB:
    • The RoleB trust policy principal arn:aws:iam::123456789012:root matches all roles in the account, but it is not specific to the RoleA ARN.
    • Because arn:aws:iam::123456789012:root does not match exactly the RoleA ARN, the policy engine checks the permissions policy of the RoleA.
    • In the RoleA permissions policy, the RoleB ARN is not in the allowed-roles-path.
    • Rusult: the RoleA cannot assume the RoleB.
  2. RoleC:
    • The RoleC trust policy principal arn:aws:iam::123456789012:role/RoleA matches exactly the RoleA ARN. The evaluation stops here.
    • Result: the RoleA can assume the RoleC.
  3. RoleD:
    • The RoleD trust policy principal arn:aws:iam::123456789012:root matches all roles in the account, but it is not specific to the RoleA ARN.
    • Because arn:aws:iam::123456789012:root does not match exactly the RoleA ARN, the policy engine checks the permissions policy of the RoleA.
    • In the RoleA permissions policy, the RoleD ARN is in the allowed-roles-path.
    • Result: the RoleA can assume the RoleD.
  4. RoleE:
    • The RoleE trust policy principal arn:aws:iam::123456789012:role/RoleA matches exactly the RoleA ARN. The evaluation stops here.
    • Result: the RoleA can assume the RoleE.

The explanation

When you assume an IAM role, if the trust policy principal matches the ARN of the role that is assuming the role, the evaluation stops. The permissions policy of the role that is assuming the role is not checked.

Note: This behavior happends only if you are assuming a role in the same account. When you do cross-account role assumption, permissions policy of the role that is assuming the role is checked.

Summary

When configuring IAM role assumption in AWS inside the same account, be aware that the trust policy of the role that is being assumed is more important than the permissions policy of the role that is assuming the role. If the trust policy principal matches the ARN of the role that is assuming the role, the evaluation stops and the role can be assumed.