I’ve gotten some responses to my article where I argue that cross-account role trust policies should trust accounts, not principals within the accounts. A lot of them took the form of “it provides an extra layer of defense” along with “it can’t hurt”. I think I adequately covered why it can hurt (that is, it provides a false sense of safety) in the article, but I want to go a little deeper on why that “extra layer” is extremely thin and not worth risking that false sense of security.
We’ll talk here only about trusting a source role, that is, not an IAM User (nor an IAM Role managed by AWS SSO) — I cover in the article why those are different.
Trusting a role instead of an account only prevents a privilege escalation when the following conditions are true:
- Access to a principal in the source account (other than the source role)
- The principal has
AssumeRole
with resource*
(never do that!), or access to change that principal’s policy to allowAssumeRole
on the destination role. Note this likely implies the ability to allowAssumeRole
on the source role in the same account. - The source role’s trust policy does not allow this principal to assume it.
- The principal cannot change the source role’s trust policy.
If any of these are not true, there is a path to privilege escalation that makes trusting the source role no better than trusting the account.
In particular, when you trust the source role, you don’t have any control over the source role’s own trust policy. For some other principal in the source account, while trusting the source role directly prevents it from being able to assume the destination role directly, if it can assume the source principal, it can then assume the destination principal anyway. So for trusting the source principal to provide additional security, we need to look at whether any other principals in the account are able to assume the source principal.
First, let’s talk about internal “threats”. One motivation to trust a source principal is that the source account is controlled by a different team than the destination account, and the team owning the destination account wants to make sure that an agreement that the source role should have access to their account doesn’t mean other things in the source account will get access.
However, as usual, technological solutions are not an adequate replacement for organizational health. If the team who owns the source account really wants to let something else access the destination account, they can just add that principal to the source principal’s trust policy, and access it that way, rather than engaging with the team to expand the destination role’s trust policy. So the team who owns the destination account is better off thinking about what’s required to trust the entire source account anyway.
Next, let’s talk about a compromised principal. This is where we get to the list of 5 conditions above. We’ll visit each one in turn.
Access to a principal in the source account (other than the source role). This is the basic assumption that we have a compromised role.
The principal must be able to assume the destination role directly, but cannot assume the source role.
- The principal has
sts:AssumeRole
on at least the destination role - The source role’s trust policy does not allow the principal to assume it
There’s one base case we can consider: the principal has AssumeRole
on resource *
. In this case, it would be allowed to directly assume the destination role if the destination role’s trust policy trusts the account, and it would not be allowed to directly assume the destination role if the destination role’s trust policy trusted only the source role, and it would not be allowed to assume the source role. In this situation, we do gain additional security by not trusting the whole account. However, the resource *
here is a bigger issue and by addressing it, we would eliminate this case.
If we assume that when the principal is compromised, it does not have permission to directly assume the destination role, we find that the condition is:
The principal must be able to make IAM changes to allow itself to directly assume the destination role, but cannot make IAM changes that allow it to assume the source role.
If the compromised principal can make IAM changes that allow it to assume the source role, then it has access to indirectly assume the destination role even when the destination role’s trust policy only trusts the source role.
If we assume the compromised principal can give itself AssumeRole
on the destination role, it likely means that it can give itself AssumeRole
on the source role, and likely means it can give itself UpdateAssumeRolePolicy
on the source role, which would then allow it to update the source role’s trust policy to allow it to assume it.
Can this be prevented with permissions boundaries? Possibly, though that permissions boundary must allow some IAM actions, and that provides its own opportunities for privilege escalation (e.g., being able to create a new role without the permissions boundary — this is why permissions boundaries are incomplete without automatic propagation, but that’s a topic for another article), in addition to the way resource policies can enable escaping permissions boundaries.
Hopefully this has clarified the very narrow path to privilege escalation that is prevented by trusting the source role directly, rather than the source account. And hopefully that clarifies why the benefits of treating the account as the security boundary outweigh the additional security given by trusting the source role directly. If you still think “well, it closes off something that shouldn’t be allowed, therefore it has to be better”, I would encourage you to think about broader risks in security posture, like the risk of overconfidence in one’s AWS IAM configuration (which is what trusting accounts rather than roles mitigates).
If you’ve got more questions or comments, you can find me on Twitter.