AWS Cross-Account Access
Tags: aws, cloudskills
Published: 2021-05-28 13:31:43

I recently set up an AWS organization to follow the best practice to use a multi-account environment ([read "Why should I set up a multi-account AWS environment?"](https://aws.amazon.com/organizations/getting-started/best-practices/)). That was quite easy using Control Tower, which sets up an organization with some accounts (and additional things) for you. BUT to use services across accounts wasn't easy for me. 

# Access-Management is Difficult
If you have used AWS before you will probably already know that managing access through permissions and policies is difficult. The possibilities are endless. Googling stuff often results in a huge amount of AWS doc pages and Stack Overflow questions or nothing at all because you have a very specific use case.

This article focuses on the possibilities for cross-account access to resources. It doesn't matter if the other account is from another company or just an account containing the production environment of your application.

ℹ️ Think of an account being a container for resources. It is a different concept of your Twitter account in which you log in with a username and password.

There are three possibilities for cross-account access:

1. Assuming a role
2. Resource-based policies
3. Access Control Lists (ACLs)

Not all of them are applicable for all services. I'll point that out later.

*⚠️ If you are in an organization, make sure no Service Control Policy (SCP) is denying access. That would end up in having no access no matter which of the three possibilities you would use.*

# How to Get Cross-Account Access?

Now you'll learn about the three possibilities mentioned earlier. Each of them including an example.

## Assume Role

In order to assume a role in the other account (let's call it `target`), we use AWS STS (Secure Token Service). When assuming a role, we receive temporary credentials which provide us with access based on the assumed role.

> Roles are the primary way to grant cross-account access.

This is the only option working for all services. This is therefore always an option. I'll guide you about when to chose which option later in this article.

**Scenario:** Allowing any identity from `source` account (`111111111111`) read access to DynamoDB table `MyTable` in the `target` account (`999999999999`).

**What do we need?**

- role in the `target` account with a policy allowing read access to `MyTable` and a policy allowing any identity from the `source` account to assume this role
- role in the `source` account with a policy allowing to assume the role in the `target` account
- the actual `MyTable` 🤪

**How does it look like?**

![assume-role.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621866057989/h9pOqD1v1.png)

Policy allowing read access (in this case only `GetItem`) to `MyTable` 
(attached to `MyTableCrossAccount`):
```json
{
  "Effect": "Allow",
  "Action": "dynamodb:GetItem",
  "Resource": "arn:aws:dynamodb:eu-central-1:999999999999:table:MyTable"
}
```

Policy allowing an identity from `source` to assume the role (attached to `MyTableCrossAccount`):
```json
{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Principal": {
    "AWS": "111111111111"
  }
}
```

ℹ️ `"AWS": "111111111111"` is a shorthand for `"AWS": "arn:aws:iam:111111111111/root"`.
 
Policy allowing to assume role in `target` (attached to `MyTableAssume`):
```json
{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Resource": "arn:aws:dynamodb:eu-central-1:999999999999:role/MyTableCrossAccount"
}
```

Assuming `MyTableCrossAccount` using the AWS CLI:
```bash
# logged in into 111111111111
aws sts assume-role --role-arn "arn:aws:iam::999999999999:role/MyTableCrossAccount" --role-session-name AWSCLI-Session

# use temporary credentials provided by the previous command
export AWS_ACCESS_KEY_ID=[from previous commands output]
export AWS_SECRET_ACCESS_KEY=[from previous commands output]
export AWS_SESSION_TOKEN=[from previous commands output]

# check the new identity
aws sts get-caller-identity
# now you can access MyTable
```

You get your temporary credentials (`aws assume-role` also return the expiry date) and can use them in order to get the permissions provided by the policy attached to the assumed role.

### BONUS: `externalId` Condition for 3rd Party Access

> In a multi-tenant environment where you support multiple customers with different AWS accounts, we **recommend using one external ID per AWS account**. This ID should be a random string generated by the third party.

It is recommended to use an external ID as a condition when given access to a third party. It prevents the so called "confused deputy" problem:

> The primary function of the external ID is to address and prevent the **"confused deputy" problem**.

I won't go into details here, but you can read more about it in AWS article [How to use an external ID when granting access to your AWS resources to a third party](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html)

## Resource-Based Policy

The next possibility is by using a resource-based policy. Resource-based policies are directly attached to the resource itself. This solution is not applicable for all services.  
Here is a [list of services with the possibility of attaching resource-based policies to them](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html). 

> Resource-based policies include a **Principal** element to specify which IAM identities can access that resource.

**Scenario:** Allowing any identity from `source` account (`111111111111`) read access to S3 bucket `MyBucket` in the `target` account (`999999999999`).

ℹ️ Resource-based policies for S3 buckets are also called bucket policies.

**What do we need?**

- bucket policy attached to `MyBucket` in the `target` account
- role in the `source` account with a policy to allow read access to `MyBucket`

**How does it look like?**

![cross-account-bucket.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1622193276746/OY2_3B3Vg.png)

Policy allowing read access (in this case only `s3:GetObject`) on `MyBucket` (attached to the role in the `source` account):
```json
{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:eu-central-1:999999999999:MyBucket/*"
}
```

Bucket policy allowing any identity from the `source` account read access (attached to `MyBucket` itself): 
```json
{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:eu-central-1:999999999999:MyBucket/*",
  "Principal": {
    "AWS": "111111111111"
  }
}
```


## Access-Control-List (ACL)
The third option is to use ACLs. ACLs are **only applicable for S3 buckets**. They can be used on bucket level (**bucket ACLs**) or on individual object level (**object ACLs**). *(Don't confuse these ACLs with Network ACLs (NACLs).*

> But ACLs support only a finite set of permissions, and these don't include all Amazon S3 permissions.

ACLs are a possibility to add cross-account access but they can't specify concrete actions (like `s3:GetObject`).

You can define ACLs under the `Permissions` tab by clicking on `Edit` in the `Access control list (ACL)` section. This is how it looks like:

![ACL.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1622195195754/FJB-eDoqI.png)

As you can see, you can grant permissions on a rather high level like `Read` and `Write`. You can add multiple grantees by clicking on `Add grantee`.

ℹ️ Be careful with the `Everyone` and `Authenticated user group` settings because they allow access from the open world.

But there is a special case in which you need an ACL:

> An object ACL is the only way to manage access to **objects that are not owned by the bucket owner**. [...] The AWS account that created the object must grant permissions using object ACLs. 

Yes, you've read right, the owner of a bucket isn't necessarily the owner of the objects in it. The identity uploading the objects is also the owner of them. A bucket owner cannot grant permissions on objects it does not own.

# When to Use What?
I pointed out three possibilities and now the question is "When should I use which solution?".

First of all, not all solutions are possible for all services. ACLs are only suitable for S3 and resource-based policies are also available for a subset of services.

The resource-based policy was the easiest one and ACLs only allow for setting higher-level permissions but have a special use case regarding the object owner.

This is my high-level decision tree:

![decision.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1622196843057/oV5z2DNgZ.png)

Please note that more advanced use cases like distinct permission on object-level, etc., are not covered here.

There is an article in the AWS S3 docs named [Access Policy Alternatives](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-alternatives-guidelines.html), which is about the three possibilities explained in this article.

# Resources

These resources are helpful:

[How IAM roles differ from resource-based policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_compare-resource-policies.html)

[Actions, resources, and condition keys for AWS services](https://docs.aws.amazon.com/service-authorization/latest/reference/reference.html)

[AWS re:Inforce 2019: The Fundamentals of AWS Cloud Security (FND209-R) on YouTube](https://youtu.be/-ObImxw1PmI)