Cross-account role trust policies should trust AWS accounts, not roles
In my article on IAM principals, I mentioned that when creating a cross-account role trust policy, it’s generally better to trust the entire account, rather than a particular principal within that account. I got some questions on why, so here are the details!
Note: there’s a follow-up to this article that goes into even more detail on the particular topic of privilege escalation.
First, let’s establish what we’re talking about. We’ve got an IAM principal (an IAM Role or IAM User) — the source principal — in the source account, and an IAM Role — the destination role — in the destination account. The destination role must have a trust policy that grants sts:AssumeRole
permission to the source principal (potentially by granting it to the entire source account), and the source principal must have a principal policy (an IAM policy attached to the principal) that grants sts:AssumeRole
for the destination role. As for all cross-account access, both sides must agree that the access is permitted!
We want our policies to be least privilege, to grant the necessary access and not allow access that is not needed. A role trust policy that trusts an entire account allows any principal with right permission to assume the role, even if only one principal inside that account needs to assume the role. So isn’t that a violation of least privilege?
What does trusting a principal protect against?
The destination role’s trust policy does not control who can use the source principal. If the source principal is an IAM Role — we’ll touch on IAM Users later—that role has its own trust policy. So potentially other principals within the account can assume the source role, and thus have access to the destination role. So privilege escalation can already happen through the source principal:
This means that by trusting the source role directly, you protect against this specific threat and only this specific threat versus trusting the account (thanks to Aidan Steele for the concise statement):
- Access to a principal in the source account (other than the source role)
- The principal does not have
AssumeRole
with resource*
(never do that!) - Access to change that principal’s policy to allow
AssumeRole
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.
That is, this only happens if a privilege escalation can happen but it doesn’t enable assuming the source principal. This scenario can happen, so isn’t it strictly better to prevent it?
Narrowly-scoped trust policies can be misleading
The argument here is that your role’s trust policy should be representative of the security boundary and not give a false sense of security.
You should plan for privilege escalation within an account. If you need to put a boundary around privilege escalation for a given principal, the account is the best boundary for that. Essentially, we are talking about thinking about cross-account access purely in terms of account-to-account relationships, and determining account structure from that.
It does not mean that granting one source principal cross-account access means any other principal in the account should be free to get access to the same destination role, any more than granting one principal access to a DynamoDB table in the same account means any other principal in the account should be able to access it. What it does mean is that any principal within the same account may be eligible for the same cross-account access.
What about if you don’t have control over the account structure? It’s still worth thinking about, from the perspective of the destination role, what the trust policy does and doesn’t have control over. Inspecting the destination role’s trust policy doesn’t provide any insight into the source principal, but it may give a false sense of security (“this role is safe because only this source principal has access”). When it trusts the entire account, access must be determined by inspection of the source account — but we already need to do that, to determine who can use the source principal. So whether you control the account structure or not, trusting the entire source account aligns more with the desired understanding of the system’s security.
When trusting a principal is okay
Now, this is general guidance, not a hard-and-fast rule. If the source principal is an IAM User actually used by a human (as opposed to used for long-lived access keys for, say, an on-prem server), the notion of “who can use the source principal?” is a little less in question than for an IAM Role. So trusting that IAM User directly is less likely to give the false sense of security discussed above.
If you’re using AWS SSO instead of IAM Users — and you should be — it’s a similar situation for trust policies. For IAM roles managed by AWS SSO, they are not modifiable from within the account (only through AWS SSO), and the trust policy only trusts the AWS SSO SAML provider (though I’d love to have control over this #awswishlist). This means that you can be sure there are not other principals that can assume the AWS SSO-managed role. So trusting it directly is also less likely to give a false sense of security. Note that trusting the role grants access to all users with permission for that role; you can use the identitystore:UserId
context key in the trust policy to specify individual users who can assume the destination role from an AWS SSO source role — though last I checked there is a bug that the context key is not populated when using a federated IdP.
If you’ve got well-secured infrastructure within the account, like common organization infrastructure protected by a service control policy, you can have more confidence about access to the source principal, and thus the destination role’s trust policy can rely on that higher confidence to use a narrow scope.
You can also include other conditions in the trust policy. There are many more useful context keys, like aws:SourceIp
and aws:MultiFactorAuthPresent
, but a good overview of that will have to wait for another article.
A final situation is trusting a principal provided by a third party — you have don’t have visibility into their security controls, so you don’t have the same problem with overconfidence in the source principal, and so closing off the threat listed at the beginning is a small bonus.
A role trust policy that trusts a specific principal suggests that only that source principal has access to it, but it does not control access to that source principal, and so makes it seem like it limits access when it may not. Instead, trusting the account is representative of the security boundary involved (that is, the boundary between accounts). In most cases this is preferable, but there are exceptions.
If you’ve got comments (or, for this article, hate mail), you can find me on Twitter.