Terraform AWS Cross-Account access

Pre-requisite

  • Two AWS accounts: AccountA and AccountB
  • IAM programmatic access user already setup and working for Terraform in AccountA, let’s call this user Terraform-User, and it already have role assigned in AccountA
  • Now that we are going to use the same Terraform-User access key and secret to work on resources in AccountB

Create a new role in AccountB

  • Trusted entity -> AWS account -> since AccountB need to trust AccountA, enter AccountA’s account ID
  • Assign required permission polices to this role, eg: AdministratorAccess
  • Assign a role name, eg: CrossAccountSignin
  • Example of the role JSON created
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::AccountA_Account_ID:root"
            },
            "Action": "sts:AssumeRole",
            "Condition": {}
        }
    ]
}
  • Note down the ARN of this role, eg:
    arn:aws:iam::AccountB_Account_ID:role/CrossAccountSignin

Create and assign policy in AccountA

  • Use following JSON definition
  • “Resource” point to the ARN of the CrossAccountSignin role created in AccountB
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": "arn:aws:iam::AccountB_Account_ID:role/CrossAccountSignin"
        }
    ]
}
  • Assign this policy to AccountA IAM user: Terraform-User

Assume role in Terraform

  • providers.tf
    Notice an alias gets created for account_b
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region = "us-east-1"
  alias  = "account_b"
  assume_role {
    role_arn = "arn:aws:iam::AccountB_Account_ID:role/CrossAccountSignin"
  }
}
  • main.tf
resource "aws_vpc" "account_a_vpc" {
  cidr_block = "10.0.1.0/24"
  tags = {
    "Name" = "account_a_vpc"
  }
}


resource "aws_vpc" "account_b_vpc" {
  provider   = aws.account_b
  cidr_block = "10.0.2.0/24"
  tags = {
    "Name" = "account_b_vpc"
  }
}
  • resource.aws_vpc.account_a_vpc will create VPC in AccountA implicitly
  • resource.aws_vpc.account_b_vpc will create VPC in AccountB by explicitly specifying provider = aws.account_b

Cross account access to data

Similarly to resource block, you can perform the same for data block, example:

  • Same providers.tf
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region = "us-east-1"
  alias  = "account_b"
  assume_role {
    role_arn = "arn:aws:iam::AccountB_Account_ID:role/CrossAccountSignin"
  }
}
  • data.tf
data "aws_availability_zones" "az_zones" {
}

data "aws_availability_zones" "app_az_zones" {
  provider = aws.account_b
}
  • data.aws_availability_zones.az_zones will retrieve availability zones as Terraform-User from AccountA
  • data.aws_availability_zones.app_az_zones” will retrieve availability zones assume role in AccountB

Cross account for module

Assume we have following folder structure:

|_ main.tf
|  providers.tf
|_ modules
    |_ app
         |_ main.tf
         |_ providers.tf

Root /providers.tf have following statement as before:

provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region = "us-east-1"
  alias  = "account_b"
  assume_role {
    role_arn = "arn:aws:iam::AccountB_Account_ID:role/CrossAccountSignin"
  }
}

Root /main.tf have following statement

module "app1" {
  source   = "./modules/app"

   .
   .
   .
}

If you run it, you may find resources gets created in the default account : AccountA, where Terraform-User is resided. How do we make the resource create in AccountB instead?

Think of module a mini block of terraform code that also require it’s own provider block. If you don’t specify anything in /modules/app/providers.tf, it will implicitly have this block, basically it’s looking for a provider called aws

provider "aws" {
}

So we will modify /main.tf like this:

module "app1" {
  source   = "./modules/app"
   providers = {
    aws = aws.account_b
  }
   .
   .
   .
}

This is telling within the module, provider.aws is equal to root provider.aws.account_b.

If you rerun terraform apply. you will notice:

  • Resources created in AccountA remains
  • New resources get created in AccountB now
  • Warning message:
Warning: Provider aws is undefined
│
│   on main.tf line 8, in module "app1":
│    8:     aws = aws.account_b
│
│ Module module.app1 does not declare a provider named aws.
│ If you wish to specify a provider configuration for the module, add an entry for aws in the required_providers block within the module.

To make Terraform happy, add following lines in /modules/app/providers.tf

terraform {
  required_providers {

    aws = {
      source = "hashicorp/aws"
    }
  }
}