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"
}
}
}