Using Multiple Terraform's Providers With AWS

Originally posted on 29 May 2020.

What Terraform Providers are?

Terraform providers are the Terraform internal component responsible for API understanding and API communication.
Providers also understand authentication and authorization for the external API.

AWS Provider

One of the most used Terraform providers is the AWS Terraform Provider. This provider handles all the communication with AWS API allowing you to provision resources on AWS.

AWS Provider Example Usage (from Terraform docs)

# Configure the AWS Provider
provider "aws" {
  region  = "us-east-1"
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Create a VPC
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

Without special needs, most users stop at these simple provider definitions, but AWS provider (like other providers) has a lot of possible customizations. Some examples:

# Configure the AWS Provider
provider "aws" {
  region  = "us-east-1"

  profile = "my-custom-aws-profile"
  shared_credentials_file = "~/.aws/credentials"

  allowed_account_ids = ["123456789012"]
  forbidden_account_ids = ["98765432198"]
}

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

Parameters like allowed_account_ids and forbidden_account_ids help you avoid applying modification on the wrong account by simply using the wrong profile or environment variables.

Multiple Providers

Now the article's main topic: using multiple AWS providers.
A single Terraform AWS provider is limited to an account and a specific region.
What if we want to deploy resources within the same stack but in two regions. This need is not as remote as you can imagine, it's actually a pretty common use case.
When you use a CloudFront Distribution with a custom SSL certificate you have to create an ACM certificate in N.Virginia.
So here we have a small example of a CloudFront distribution in Ireland and the needed ACM Certificate in N.Virginia, all within the same stack and even within the same file.

provider "aws" {
  region = "eu-west-1"
}

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

terraform {
  required_version = "= 0.15.4"

   required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

To use our multiple providers:

resource "aws_cloudfront_distribution" "distribution" {
    viewer_certificate_arguments {
        acm_certificate_arn = aws_acm_certificate.cert.arn
        ....
    }
    ....
}

resource "aws_acm_certificate" "cert" {
    provider = aws.uswest
    ....
}

Example (S3 Cross-Region Replication)

resource "aws_s3_bucket" "uswest_bucket" {
  provider = aws.uswest
  bucket   = "alessandromarinoac-uswest-bucket"
  acl      = "private"
  versioning {
    enabled = true
  }
}

resource "aws_s3_bucket" "euwest_bucket" {
  bucket = "alessandromarinoac-euwest-bucket"
  acl    = "private"
  versioning {
    enabled = true
  }

  replication_configuration {
    role = aws_iam_role.s3_fullaccess_role.arn
    rules {
      id     = "testReplication"
      status = "Enabled"
      destination {
        bucket        = aws_s3_bucket.uswest_bucket.arn
        storage_class = "STANDARD"
      }
    }
  }
}

resource "aws_iam_role" "s3_fullaccess_role" {
  name               = "s3-fullaccess-role"
  assume_role_policy = data.aws_iam_policy_document.s3_fullaccess_role_assume_policy.json
}

resource "aws_iam_role_policy" "s3_fullaccess_policy" {
  name   = "s3_fullaccess_policy"
  role   = aws_iam_role.s3_fullaccess_role.id
  policy = data.aws_iam_policy_document.s3_fullaccess_role_policy.json
}

data "aws_iam_policy_document" "s3_fullaccess_role_assume_policy" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["s3.amazonaws.com"]
    }
    actions = [
      "sts:AssumeRole"
    ]
  }
}

data "aws_iam_policy_document" "s3_fullaccess_role_policy" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetReplicationConfiguration",
      "s3:ListBucket"
    ]
    resources = [
      "arn:aws:s3:::alessandromarinoac-euwest-bucket",
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:GetObjectVersion",
      "s3:GetObjectVersionAcl",
      "s3:GetObjectVersionTagging"
    ]
    resources = [
      "arn:aws:s3:::alessandromarinoac-euwest-bucket/*"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:ReplicateObject",
      "s3:ReplicateDelete",
      "s3:ReplicateTags"
    ]
    resources = [
      "${aws_s3_bucket.uswest_bucket.arn}/*"
    ]
  }
}

The example will create two S3 buckets in two different AWS regions:

Terraform Multi Provider Buckets

With replication enabled:

Terraform Multi Provider Replication