AWS Security Blog
How to Use External ID When Granting Access to Your AWS Resources
When you need to grant access to your AWS resources to a third party, we recommend you do so using an IAM role with external ID. In this post, Josh Bean, a programmer writer on the AWS Identity and Access Management (IAM) team, walks you through a scenario to show you how.
At times, you will want to provide a third party with access to your AWS resources. A recommended best practice is to use IAM roles. If you haven’t used roles before, they provide a mechanism to grant access to your AWS resources without needing to share long-term credentials (for example, an IAM user’s access key). Let’s say you want to use an offering from a member of the AWS Partner Network (APN) that monitors your AWS account and provides advice to optimize costs. In order to track your daily spending, the APN Partner (Partner) will need access to your AWS resources. Though you could provide that Partner with the credentials of an IAM user, we highly recommend you use a role. You can learn more about roles in the IAM user guide.
In this post, I’ll describe the scenario of granting a third party access to your AWS resources, and I’ll focus on one important but less known aspect of this scenario: the external ID. If you haven’t come across this term before, or if you have and want to know more, this post is for you!
What is the external ID?
At a high level, the external ID is a piece of data that can be passed to the AssumeRole API of the Security Token Service (STS). You can then use the external ID in the condition element in a role’s trust policy, allowing the role to be assumed only when a certain value is present in the external ID. In the following section I’ll walk through a simple scenario that will help you better understand how to use the external ID.
What is the external ID used for?
The primary function of the external ID is to address the confused deputy problem. In abstract terms, the external ID allows the user that is assuming the role to assert the circumstances in which they are operating. It also provides a way for the account owner to permit the role to be assumed only under specific circumstances.
A typical scenario is this: a Partner requires access to certain resources to perform AWS tasks on your behalf. The Partner has many customers, and it needs a way to access each customer’s AWS resources. The Partner follows IAM best practices by not asking its customers for their AWS account access keys, which are secrets that should never be shared. Instead, the Partner requests a role ARN (Amazon Resource Name) from each customer. But if another AWS customer were able to guess or obtain your role ARN, that customer could then use your role ARN to gain access to your AWS resources by way of the Partner. Let’s dig into how this could occur and how you can use the external ID to prevent it.
The confused deputy problem
This scenario demonstrates how an unauthorized user could gain access to your AWS resources by exploiting the confused deputy problem. In this scenario:
- AWS1 is your AWS account.
- AWS1:ExampleRole is a role in your account. This role’s trust policy trusts the Partner (Example Corp) by specifying Example Corp’s AWS account as the one that can assume the role.
When you start using Example Corp’s service, you provide the ARN of AWS1:ExampleRole to Example Corp (step 1 in the following diagram). Example Corp uses that role ARN to obtain temporary security credentials to access resources in your AWS account (2). In this way, you are trusting Example Corp as a “deputy” that can act on your behalf. Another AWS customer also starts using Example Corp’s service, and this customer also provides the ARN of AWS1:ExampleRole for Example Corp to use (3). (We assume here that the other customer learned or guessed the ARN of AWS1:ExampleRole, which isn’t a secret.) When the other customer asks Example Corp to access AWS resources in “its” account, Example Corp will use AWS1:ExampleRole to access resources in your account (4). This is how the other customer could gain unauthorized access to your resources. Because this other customer was able to trick Example Corp into unwittingly acting on your resources, Example Corp is now a “confused deputy.” The following diagram illustrates the confused deputy problem.
How does the external ID prevent this?
You address the confused deputy problem by including the external ID condition in the role’s trust policy. The external ID corresponds to a unique customer identifier that is generated by Example Corp. The external ID value must be unique among Example Corp’s customers, and this is why you get it from Example Corp (you don’t come up with the external ID value on your own). Only Example Corp is in a position to guarantee unique external ID values for each of its customers.
In our scenario, Example Corp’s unique identifier for you is “12345”, and its identifier for the other customer is “67890”. (These identifiers are simplified for this scenario. Generally, these identifiers are GUIDs. Assuming that these identifiers are unique among Example Corp’s customers, they are sensible values to use for the external ID.) Example Corp gives the external ID value of “12345” to you, and you then add to the role’s trust policy a Condition element that requires the external ID to be 12345, like this:
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": {"AWS": "Example Corp's AWS Account ID"}, "Action": "sts:AssumeRole", "Condition": {"StringEquals": {"sts:ExternalId": "12345"}} } }
The Condition element in this policy allows Example Corp to assume the role only when the AssumeRole API call includes the external ID value of “12345”. Example Corp makes sure that whenever it assume a role on behalf of a customer, it always includes that customer’s external ID value in the AssumeRole call, as a means of asserting the circumstances in which it is acting.
Let’s walk through the same scenario, but this time using external ID to prevent an unauthorized customer from gaining access to your resources. As before, when you start using Example Corp’s service, you provide the ARN of AWS1:ExampleRole to Example Corp (step 1 in the following diagram). When Example Corp uses that role ARN to assume the role AWS1:ExampleRole, Example Corp includes your external ID (“12345”) in the AssumeRole API call. The external ID matches the role’s trust policy, so the AssumeRole API call succeeds and Example Corp obtains temporary security credentials to access resources in your AWS account (2). Another AWS customer also starts using Example Corp’s service, and as before, this customer also provides the ARN of AWS1:ExampleRole for Example Corp to use (3). But this time, when Example Corp attempts to assume the role AWS1:ExampleRole, it provides the external ID associated with the other customer (“67890”). Example Corp does this because the request to use the role came from the other customer, so “67890” indicates the circumstance in which Example Corp is acting. Because you’ve added a condition with your own external ID (“12345”) to the trust policy of AWS1:ExampleRole, the AssumeRole API call will fail, and the other customer is prevented from gaining unauthorized access resources in your account (indicated by the big red “X” in step 4 of the diagram). The external ID helps to prevent any other customer from tricking Example Corp into unwittingly accessing your resources—it mitigates the confused deputy problem. The following diagram illustrates this scenario.
It might help to think of the external ID as the way that the assumer of a role specifies who they are acting on behalf of, and the way that the role owner enforces that their role can be assumed only when the assumer is acting on the role owner’s behalf. In the preceding scenario, Example Corp is always acting on behalf of one of its customers, so it uses the external ID to declare which customer it is acting on behalf of. In the same way, Example Corp’s customers (that’s you, in our scenario) use the external ID in their role’s trust policy as a way of enforcing that “Example Corp is allowed to assume my role only when it is acting on my behalf.”
When should I use the external ID?
If you are an AWS account owner and you have configured a role for a third party, you should ask the third party for the external ID that it will include when it assumes your role. Then you should use that external ID in your role’s trust policy to ensure that the external party can assume your role only when it is acting in the circumstances you intend (that is, when it’s acting on your behalf).
If you are in the position of assuming roles in different circumstances (for example, on behalf of different customers) like the Partner in our scenario, then you should assign a unique external ID to each of your customers and instruct them to add the external ID to their role’s trust policy. You probably already have a unique identifier for each of your customers, and this unique ID is sufficient for use as an external ID. The external ID is not a special value that you need to create explicitly, or track separately, just for this purpose. You should always specify the external ID in your AssumeRole API calls, and we also recommend that when a customer gives you a role ARN, you try to assume the role both with and without the correct external ID. If you can assume the role without the correct external ID, don’t store the customer’s role ARN in your system until your customer has updated the role trust policy to require the correct external ID. This way, you help your customers to do the right thing, which helps to keep both of you protected against the confused deputy problem.
Conclusion
We hope this post helped you better understand the external ID, and motivates you to start using it, if you’re not already. We recommend that you always use an external ID in every third-party delegation scenario.
If you have questions or comments, please post them on the AWS IAM Forum.
– Josh