How Do Use AWS Cross Account Resource Access

In my previous post, I explored how to use IAM roles to authenticate with Amazon RDS. But what if you need to access resources in a different AWS account? This is where cross-account access comes in - it enables resources in one account to securely access resources in another.

According to the official doc, there are many benefits of using multiple AWS accounts:

  • Account Isolation: Multiple accounts help isolate different environments (e.g., dev, staging, prod) and projects, reducing the risk of accidental changes or data breaches.
  • Cost Allocation and Tracking: Separating costs by account allows for better tracking and allocation of expenses, making it easier to manage budgets and optimize spend.
  • Security and Compliance: Using separate accounts for different purposes (e.g., dev vs. prod) helps ensure compliance with security policies and regulations, reducing the risk of unauthorized access or data breaches.
  • Operational Efficiency: Multiple accounts enable more efficient operations by allowing teams to work independently without affecting other projects or environments.
  • Governance and Management: Having separate accounts for different purposes helps establish clear governance and management structures, making it easier to manage a large number of AWS resources.

In this post, I’ll explain how cross-account access works and use terraform to demonstrate how to set up IAM roles in different accounts.

Let’s get started.

Concept

Here is a simple diagram demonstrate how this works.

In this example, we have two AWS accounts:

  • Account 1 (Trusted): This account contains the users or roles that need to access resources in another account.
  • Account 2 (Trustee): This account owns the resources that need to be accessed from Account 1.

Here’s how it works:

In Account 1, IAM assigns a role (let’s call it Role 1) to an EC2 instance. This EC2 instance can then use Role 1 to assume a role in Account 2 (Role 2). Once the assumption is successful, the EC2 instance can use the temporary credentials from the returned response to access resources in Account 2.

In the next section, I will show you how to access secrets in Account 2.

Terraform

We will use a terraform to set up IAM roles in both accounts.

IAM role in account 1

Below Terraform code defines an IAM role and policies that enable EC2 instances in your trusted account (Account 1) to assume roles in trustee account (Account 2).

Specifically, it:

  1. Creates IAM role ec2-read-secrets and allows EC2 instances in Account 1 to assume this role.
  2. Creates IAM policy assume-account-two that permits the assumption of the “secret-readonly“ role in Account 2.
  3. Attaches assume-account-two to the “ec2-read-secrets“ role.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
resource "aws_iam_role" "ec2_cross_account" {
name = "ec2-read-secrets"
assume_role_policy = data.aws_iam_policy_document.ec2_cross_account.json
}

data "aws_iam_policy_document" "assume_account_two" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
resources = ["arn:aws:iam::<ACCOUNT_TWO_NUMBER>:role/secret-readonly"]
}
}

data "aws_iam_policy_document" "ec2_cross_account" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

resource "aws_iam_policy" "assume_account_two" {
name = "assume-account-two"
description = "Allows EC2 instances to assume role in account 2"
policy = data.aws_iam_policy_document.assume_account_two.json
}

resource "aws_iam_role_policy_attachment" "attachment" {
role = aws_iam_role.ec2_cross_account.name
policy_arn = aws_iam_policy.assume_account_two.arn
}

IAM role in account 2

Now let’s go to account 2 and see what we need to do.

  1. Creates IAM role secret-readonly.
  2. Create IAM role policy ec2_cross_account to allow role ec2-read-secrets in Account 1 to assume.
  3. Creates IAM policy that allow read only access to a specific secrets. (Example taken from official doc)
  4. Attaches both IAM role policy and IAM policy to the role.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
data "aws_iam_policy_document" "ec2_cross_account" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "AWS"
identifiers = ["arn:aws:iam::<ACCOUNT_ONE_NUMBER>:role/ec2-read-secrets"]
}
}
}

data "aws_iam_policy_document" "secret_readonly" {
statement {
effect = "Allow"
actions = [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
]
resources = [
aws_secretsmanager_secret.secrets.arn
]
}

statement {
effect = "Allow"
actions = ["secretsmanager:ListSecrets"]
resources = ["*"]
}
}

resource "aws_iam_role" "secret_readonly" {
name = "secret-readonly"
assume_role_policy = data.aws_iam_policy_document.ec2_cross_account.json
}

resource "aws_iam_policy" "secret_readonly" {
name = "secret-readonly"
description = "Allows role to read secret"
policy = data.aws_iam_policy_document.secret_readonly.json
}

resource "aws_iam_role_policy_attachment" "attachment" {
role = aws_iam_role.secret_readonly.name
policy_arn = aws_iam_policy.secret_readonly.arn
}
1
2
3
4
output "role_arn" {
description = "The ARN of the role that can read secrets"
value = aws_iam_role.secret_readonly.arn
}

Demo

Now that with the roles set up, let’s see how it works. For simplicity, I am using aws cli for the demo.

Validation

To verify our set up, follow these steps

  • SSH to EC2 in Account 1.
    The EC2 instance should have Role 1 assigned to its instance rofile.
  • Assume Role 2
    1
    2
    3
    4
    aws sts assume-role \
    --role-arn arn:aws:iam::<ACCOUNT_TWO_NUMBER>:role/secret-readonly \
    --role-session-name demo \
    --output json

If your setup is correct, this command should execute successfully and return credentials in JSON format.

Access secret

Now let’s access the secret.

  • Store the sts API call response.

    1
    2
    3
    4
    SESSION=$(aws sts assume-role \
    --role-arn arn:aws:iam::<ACCOUNT_TWO_NUMBER>:role/secret-readonly \
    --role-session-name demo \
    --output json)
  • Save credentials to environment variables.

    1
    2
    3
    export AWS_ACCESS_KEY_ID=$(echo $SESSION | jq -r .Credentials.AccessKeyId)
    export AWS_SECRET_ACCESS_KEY=$(echo $SESSION | jq -r .Credentials.SecretAccessKey)
    export AWS_SESSION_TOKEN=$(echo $SESSION | jq -r .Credentials.SessionToken)
  • Access secrect in Account 2.

    1
    2
    aws secretsmanager get-secret-value \
    --secret-id <SECRET_NAME>

That’s all I want to share with you today. See you next time.