Cross Account IAM: Resource Policies
Let's say you have some resources in one account - maybe an ECR repository with container images.
Then you have another account that needs to use that ECR repository - maybe your Kubernetes needs to pull a container image.
We need the second account to be able to pull ECR images from the first account.
How many goats do you have to sacrifice to appease the IAM gods? (11 normally, but 33 if you're creating Roles for Kubernetes and have to learn what OIDC is).
Gather your goats!
In this article, we're going to see how to accomplish this with Resource Policies. In another article we'll see how to do it using just IAM Roles.
Both Accounts Need to Agree
It's important to keep in mind that with all solutions, both accounts need to agree that they can interact with the resources in question. It's a bit like a contract. One account has to grant access to the other account, and the other account has to decide it's allowed to use that granted access.
This isn't always obvious, especially if one side is using an AdministratorAccess
role (or similar), which effectively grants all access to everything using via IAM wildcards *
. This implicitly fulfills its half of the "contract".
In any case, there are two(ish) ways to accomplish cross-account IAM:
- Resource Policies
- Role assumption
We'll talk about them both. This particular article will discuss Resource Policies.
Resource Policies
Resource policies are permissions you attach directly to a resource, such as an S3 bucket, SQS queue, ECR repository, or whatever other resource supports resource policies (relatively few of them!).
If you're doing some click-ops, you won't be in the IAM control panel when editing resource policies. You'll be editing a specific resource (a specific S3 bucket, for example).
In all cases, when adding a policy directly to a resource, we already know what resource we're affecting (the resource the policy is being added to). What we need to do is define who can interact with that specific resource. This is done via the Principal
section of the resource policy.
Let's see what that looks like.
Example Resource Policies
Keeping with our example of cross-account access to an ECR repository, let's see some ECR resource policies.
If account-a
owns the ECR repository, and account-b
wants to access it, we can add the following resource policies (or some varient of them) to the ECR repository.
Account-based Access
This policy will let any principal of account-b
to pull an ECR image.
1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Sid": "AllowPull", 6 "Effect": "Allow", 7 "Principal": { 8 "AWS": [ 9 "arn:aws:iam::<account-b-id>:root"10 ]11 },12 "Action": [13 "ecr:GetAuthorizationToken",14 "ecr:BatchGetImage",15 "ecr:BatchCheckLayerAvailability",16 "ecr:GetDownloadUrlForLayer",17 "ecr:DescribeImages",18 "ecr:DescribeRepositories"19 ]20 }21 ]22}
The AWS Principal here ("arn:aws:iam::<account-b-id>:root"
) lets any principal (user, role, etc) in account-b
to take action on this ECR repository. Yes, that's true despite the use of root
there. We could also use this Principal
, which is equivalent (and to me, a bit more clear):
1"Principal": {2 "AWS": ["account-b-id"]3},
Role-based Access
As a general statement, we may not (should not!) use IAM users with access keys if we can help it. Instead, we might use Roles. If account-b
has a Role, account-a
can allow that Role to enact upon the ECR repository.
The following Resource Policy only allows a specific Role in account-b
to enact on this ECR repository. It specifies a Role ARN as its principal:
1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Sid": "AllowPull", 6 "Effect": "Allow", 7 "Principal": { 8 "AWS": [ 9 "arn:aws:iam::<account-b-id>:role/some-role-name"10 ]11 },12 "Action": [13 "ecr:GetAuthorizationToken",14 "ecr:BatchGetImage",15 "ecr:BatchCheckLayerAvailability",16 "ecr:GetDownloadUrlForLayer",17 "ecr:DescribeImages",18 "ecr:DescribeRepositories"19 ]20 }21 ]22}
Organization-based Access
Here's another example - we can use a Condition
to restrict further who can interact with a resource. Conditions can do quite a lot. The following is just one example!
If you're using AWS Organizations to manage multiple accounts, you can actually set organization paths to allow permissions to a resource. Here's a Resource policy for a given ECR repository that allows accounts within a given OU this access:
1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Sid": "AllowPull", 6 "Effect": "Allow", 7 "Principal": "*", 8 "Action": [ 9 "ecr:GetAuthorizationToken",10 "ecr:BatchGetImage",11 "ecr:BatchCheckLayerAvailability",12 "ecr:GetDownloadUrlForLayer",13 "ecr:DescribeImages",14 "ecr:DescribeRepositories"15 ],16 "Condition": {17 "ForAnyValue:StringLike": {18 "aws:PrincipalOrgPaths": "o-xxx/r-yyy/ou-aaa-bbb/ou-ccc-ddd/*"19 }20 }21 }22 ]23}
What is going on here!? Well, we're allowing any Principal *
(any IAM user or Role) access to our ECR repository as long as the account trying to interact with ECR is within our specific organization. In fact, it has to be within a specific OU path (which includes any of its child OU's).
We have to reference them by ID, so you need to find those within your Organization Management account. The pattern is something like this:
1# My Org ID / Root ID / Workloads / Staging / Any sub OU2"o-xxx / r-yyy / ou-aaa-bbb / ou-ccc-ddd /*"
We allow accounts within (and underneath) our OU workloads > staging
. The OU's could be of any depth, depending on your OU / account structure.
So if your Organization accounts were structured like these OU's (and the accounts within them):
1root 2├── sandbox 3└── workloads 4 ├── production 5 │ ├── app-a 6 │ │ └── [prd-account-app-a] 7 │ └── app-b 8 │ └── [prd-account-app-b] 9 └── staging10 ├── app-a11 │ └── [stg-account-app-a]12 └── app-b13 └── [stg-account-app-b]
...the above example would allow accounts stg-account-app-a
and stg-account-app-b
access to this ECR repository, but it would disallow prd-account-app-a
and prd-account-app-b
.
More Examples
For more ideas, here are more examples from the docs of resource polices for ECR. You can mix and match these ideas together as well!
Account B's Requirements
Now, remember - cross-account permissions require both accounts to agree.
In the above examples, account-a
has allowed account-b
to use this ECR repository in some way.
However, account-b
still needs a Policy attached to a Role, User, or Group that allows the usage of this ECR repository to that Principal.
For example, if we created that Role from above in account-b
that was named some-role-name
, it would need an attached Policy similar to the following to complete the "contract" and thus be allowed use of the ECR repository in account-a
:
1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Sid": "AllowPull", 6 "Effect": "Allow", 7 "Resource": [ 8 "<account-a-id>.dkr.ecr.<region>.amazonaws.com/<ecr-repo-name>" 9 ],10 "Action": [11 "ecr:BatchGetImage",12 "ecr:BatchCheckLayerAvailability",13 "ecr:GetDownloadUrlForLayer",14 "ecr:DescribeImages",15 "ecr:DescribeRepositories"16 ]17 },18 {19 "Sid": "QuarkyECRNeedsThisToBeASeparateStatement",20 "Effect": "Allow",21 "Resource": [22 "*"23 ],24 "Action": [25 "ecr:GetAuthorizationToken",26 ]27 }28 ]29}
The first statement there does the main work here - it says that we're allowed to do these actions above on the ECR repository in account-a
.
The second statement is a quark of ECR - the permission to get an authorization token needs to be on Resource: "*"
and is often therefore in its own statement.
AWS is complex. Sign up for free, useful lessons like this.