In the world of Infrastructure as Code (IAC), Terraform has emerged as a powerful tool for provisioning and managing cloud resources across various cloud providers. Often, we encounter scenarios where we need to manage resources spanning multiple AWS accounts. In this blog post, we’ll explore how to create a versatile Terraform module that enables cross-account resource management by allowing you to pass credentials for multiple AWS accounts.
We have seen a lot of examples of how to create a terraform module but sometimes we need to interact with more than one account at once in order to fulfill the requirement. below are a few examples where you need a cross-account terraform module.
The use cases mentioned above are only a few examples, cross account modules are not only limited to this. It can be implemented in any custom module as well where we need interaction between more than one account.
For this article, I will take an example of VPC peering. you can take reference from it and use it anywhere.
First of all, I will show you what a VPC peering module for a single account looks like.
# provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-south-1"
profile = "my-first-account"
}
In my provider.tf I have just provided information for one of my AWS account with the profile name my-first-account
Now Let’s see how I am calling the module:
# vpc-peering.tf
module "vpc-peering-same-account-1-account2" {
source = "../../modules/vpc-peering/same-account"
requestor_vpc_id = "ENTER VPC ID OF THE FIRST VPC HERE"
acceptor_vpc_id = "ENTER VPC ID OF THE SECOND VPC HERE"
peer_region = "YOUR AWS REGION"
}
In the above file I am just calling the VPC peering module and providing the information like VPC ID and aws region.
Now let’s see how things are looking inside the module. below is my variable.tf of the module.
# variable.tf inside module
variable "peer_region" {}
variable "requestor_vpc_id" {}
variable "acceptor_vpc_id" {}
data "aws_vpc" "requestor" {
id = var.requestor_vpc_id
}
data "aws_route_tables" "requestor" {
vpc_id = var.requestor_vpc_id
}
data "aws_vpc" "acceptor" {
id = var.acceptor_vpc_id
}
data "aws_route_tables" "acceptor" {
vpc_id = var.acceptor_vpc_id
}
In the above file, I am just declaring the variables that are required inside the module and I have also declared two data variables that will provide me the information of route tables.
Now Let’s see how my actual module file looks like. which is creating the VPC peering.
# main.tf inside module
# Requester's side of the connection.
resource "aws_vpc_peering_connection" "peer" {
peer_owner_id = data.aws_caller_identity.peer.account_id
peer_region = var.peer_region
vpc_id = var.requestor_vpc_id
peer_vpc_id = var.acceptor_vpc_id
auto_accept = false
}
# Accepter's side of the connection.
resource "aws_vpc_peering_connection_accepter" "peer" {
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
auto_accept = true
}
#Module : ROUTE REQUESTOR
#Description : Create routes from requestor to acceptor.
resource "aws_route" "requestor_vpc_peering" {
count = length(data.aws_route_tables.requestor.ids)
route_table_id = tolist(data.aws_route_tables.requestor.ids)[count.index]
destination_cidr_block = data.aws_vpc.acceptor.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
#Module : ROUTE ACCEPTOR
#Description : Create routes from acceptor to requestor.
resource "aws_route" "acceptor_vpc_peering" {
count = length(data.aws_route_tables.acceptor.ids)
route_table_id = tolist(data.aws_route_tables.acceptor.ids)[count.index]
destination_cidr_block = data.aws_vpc.requestor.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
The above module is doing 4 operations.
Earlier my provider.tf had credentials for only one AWS account. Now I have to update the same with the credentials of my second AWS account as well by adding the below snippet additionally into my provider.tf file
provider "aws" {
region = "ap-south-1"
profile = "my-second-account"
alias = "my-second-account"
}
After adding the above snippet my final file will look something like this
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-south-1"
profile = "my-first-account"
}
provider "aws" {
region = "ap-south-1"
profile = "my-second-account"
alias = "my-second-account"
}
Now I will have to both credentials while calling my module by adding an additional providers block. See the updated file below.
module "vpc-peering-cross-account-1-account2" {
source = "../../modules/vpc-peering/cross-account"
requestor_vpc_id = "ENTER VPC ID OF THE FIRST ACCOUNT HERE"
acceptor_vpc_id = "ENTER VPC ID OF THE SECOND ACCOUNT HERE"
peer_region = "YOUR REGION"
providers = {
aws.requestor = aws
aws.acceptor = aws.my-second-account
}
}
In the providers block I am passing two credentials with the name
aws.requestor
and aws.acceptor
In the requestor, I am using the default credentials with name
aws
only, and for the acceptor I am using aliased credentials with name
aws.my-second-account
Now let’s jump on to the module and see how things have been changed there.
While calling the module I am passing the two credentials with the name
aws.requestor
and aws.acceptor
. Similarly, I will have to make the module aware that It
should expect credentials for two different accounts with name requestor and acceptor this name
requestor and acceptor are totally my choice, you can name them anything you want. This is
totally up to you.
Now let’s see the updated variables.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
configuration_aliases = [aws.requestor, aws.acceptor]
}
}
}
variable "peer_region" {}
variable "requestor_vpc_id" {}
variable "acceptor_vpc_id" {}
data "aws_vpc" "requestor" {
provider = aws.acceptor
id = var.acceptor_vpc_id
}
data "aws_route_tables" "requestor" {
provider = aws.requestor
vpc_id = var.requestor_vpc_id
}
data "aws_vpc" "acceptor" {
provider = aws.acceptor
id = var.acceptor_vpc_id
}
data "aws_route_tables" "acceptor" {
provider = aws.acceptor
vpc_id = var.acceptor_vpc_id
}
Along with adding the required_provider block, I am also passing the provider information in the data variables that I was using to get the route table information.
Now we are only left with one last change
As we show how we have updated the data variables with the provider information so that the can use the credentials of the appropriate account. Similarly, we have updated our resource definitions so that they can also be created in the respective account. see the updated code snippet below.
# Requester's side of the connection.
resource "aws_vpc_peering_connection" "peer" {
peer_owner_id = data.aws_caller_identity.peer.account_id
peer_region = var.peer_region
vpc_id = var.requestor_vpc_id
peer_vpc_id = var.acceptor_vpc_id
auto_accept = false
provider = aws.requestor
}
# Accepter's side of the connection.
resource "aws_vpc_peering_connection_accepter" "peer" {
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
auto_accept = true
provider = aws.acceptor
}
#Module : ROUTE REQUESTOR
#Description : Create routes from requestor to acceptor.
resource "aws_route" "requestor_vpc_peering" {
count = length(data.aws_route_tables.requestor.ids)
route_table_id = tolist(data.aws_route_tables.requestor.ids)[count.index]
destination_cidr_block = data.aws_vpc.acceptor.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
provider = aws.requestor
}
#Module : ROUTE ACCEPTOR
#Description : Create routes from acceptor to requestor.
resource "aws_route" "acceptor_vpc_peering" {
count = length(data.aws_route_tables.acceptor.ids)
route_table_id = tolist(data.aws_route_tables.acceptor.ids)[count.index]
destination_cidr_block = data.aws_vpc.requestor.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
provider = aws.acceptor
}
With this last change, I will be able to create a cross-account VPC peering in one go. I don’t have to go to two separate places in order to do the same.
You can use this cross-account module for a single account as well. you
only have
to pass the same credentials in place of both aws.requestor
and
aws.acceptor
See the example below.
module "vpc-peering-cross-account-1-account2" {
source = "../../modules/vpc-peering/cross-account"
requestor_vpc_id = "ENTER VPC ID OF THE FIRST ACCOUNT HERE"
acceptor_vpc_id = "ENTER VPC ID OF THE SECOND ACCOUNT HERE"
peer_region = "YOUR REGION"
providers = {
aws.requestor = aws
aws.acceptor = aws
}
}
Refer to previous series on Terraform best practices