2 min read

External ID policy review

Granting 3rd parties access to your AWS resources via roles should always use external ID condition. If a vendor asks you to provision an IAM user with access + secret key in 2023, they're doing it wrong.

External IDs are used as part of the condition block on a role's trust policy, which is another name for an IAM role's resource policy. It controls cross-account access to assume a role by principals in other zones of trust/AWS accounts.

In this example, the role this trust policy is attached to will allow principals in the AWS account 123456789012 to assume the role using the sts:AssumeRole permission, as long as they include and match the external ID "94d44a42-eb49-4944-8a09-47fd68e2dbd5".

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "94d44a42-eb49-4944-8a09-47fd68e2dbd5"
        }
      }
    }
  ]
}

This external ID is just a UUID, but it can be any string value. This role can be assumed programatically using the SDKs or via a CLI command with the appropriate parameters:

aws sts assume-role \
    --role-arn arn:aws:iam::111111111111:role/RoleName \
    --role-session-name SessionNameIsRequired \
    --external-id 94d44a42-eb49-4944-8a09-47fd68e2dbd5

As the example suggests, you must include the role session name and it must be unique, even though it doesn't impact the session directly. The session name is used for logging purposes, and makes it easy to track in CloudTrail what was done by the assumed role, even if there are concurrent sessions.

Buy Why?

Using an external ID prevents the confused deputy problem, similar to what the iam:PassRole permission does for AWS services:

AWS IAM:PassRole explained
A quick and easy-to-follow explanation of IAM:PassRole on AWS (with diagrams!)

This condition acts makes sure that the delegated/trusted party double-checks that they're using the right delegation when accessing your account. If the external ID supplied as part of the assume role command doesn't match what the resource policy condition the API call will fail, just like any other policy with a condition blog. Without the external ID, a malicious user of the vender could potentially use the service to access a different customers' AWS resources.

You shouldn't be able to set the external ID. If you can, there's no point to having it! Even big security companies like Trend Micro have gotten this wrong in the past:

If you previously added an AWS account using a cross-account role, you might have specified a user-defined external ID. To better align with AWS best-practices, Trend Micro recommends switching to the Workload Security-defined external ID.

The external ID is not a secret, and is not treated as such by AWS. Similar to your AWS account number, it's not bad for external parties to know it (but I probably wouldn't be handing it out on the street either). Being able to re-generate the external ID is OK, as long as you can't actually set it, and it has a sufficient amount of randomness to it (e.g. a UUID or similar).

📙
Want to understand AWS IAM?
Check out my book: The Practical AWS IAM Guide