Skip to main content

Terraform Provider for AWS Version 6: S3 Cross-Region Replication

·707 words·4 mins
Terraform Aws

Version 6 of the AWS provider for Terraform is available in beta.

The biggest change in this version is that it supports multiple AWS regions in the same provider instance.

Previously you would instead do something like this to work with multiple (two) regions:

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

provider "aws" {
  region = "eu-west-2"
  alias  = "london"
}

resource "aws_s3_bucket" "source" {
  bucket_prefix = "source"
}

resource "aws_s3_bucket" "destination" {
  provider      = aws.london
  bucket_prefix = "destination"
}

The provider with no explicit alias is the default provider which will be used unless the provider argument is explicitly used to select a different provider alias.


In this blog post I will show how you can set up S3 bucket cross-region replication with version 6 of the AWS provider for Terraform.

Configure the provider
#

Specify that your Terraform configuration will use the current beta version of the AWS provider (at the time of writing it is 6.0.0-beta2):

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "6.0.0-beta2"
    }
  }
}

You can still include a default configuration for your provider if you wish, for instance you could add the following provider block:

provider "aws" {
  region = "eu-west-1"
  # other configuration options left out ...
}

In the following I will skip configuring the provider with any defaults, just to highlight how I configure different regions for different resources. This, I leave my provider configuration empty (I could also have left it out completely):

provider "aws" {}

I want to set up replication between Ireland (eu-west-1) and London (eu-west-2). I define local values for the two regions:

locals {
  source_region      = "eu-west-1"
  destination_region = "eu-west-2"
}

Configure the source bucket
#

Resources that are regional now supports the region argument. The source S3 bucket is configured as follows:

resource "aws_s3_bucket" "source" {
  region        = local.source_region
  bucket_prefix = "source"
}

resource "aws_s3_bucket_versioning" "source" {
  bucket = aws_s3_bucket.source.bucket
  region = local.source_region

  versioning_configuration {
    status = "Enabled"
  }
}

You must enable bucket versioning when setting up cross-region replication.

Configure the destination bucket
#

The destination S3 bucket is configured in a similar fashion, with the notable difference being that it is placed in a different region:

resource "aws_s3_bucket" "destination" {
  region        = local.destination_region
  bucket_prefix = "destination"
}

resource "aws_s3_bucket_versioning" "destination" {
  bucket = aws_s3_bucket.destination.bucket
  region = local.destination_region

  versioning_configuration {
    status = "Enabled"
  }
}

Set up IAM resources
#

S3 requires an IAM role that it can assume to perform the object replication:

data "aws_iam_policy_document" "assumerole" {
  statement {
    effect = "Allow"

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

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "replication" {
  name_prefix        = "s3-replication"
  assume_role_policy = data.aws_iam_policy_document.assumerole.json
}

You must also create a replication IAM policy and attach it to the new IAM role:

data "aws_iam_policy_document" "replication" {
  statement {
    effect = "Allow"

    actions = [
      "s3:GetReplicationConfiguration",
      "s3:ListBucket",
    ]

    resources = [
      aws_s3_bucket.source.arn,
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "s3:GetObjectVersionForReplication",
      "s3:GetObjectVersionAcl",
      "s3:GetObjectVersionTagging",
    ]

    resources = [
      "${aws_s3_bucket.source.arn}/*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "s3:ReplicateObject",
      "s3:ReplicateDelete",
      "s3:ReplicateTags",
    ]

    resources = [
      "${aws_s3_bucket.destination.arn}/*",
    ]
  }
}

resource "aws_iam_policy" "replication" {
  name_prefix = "replication"
  policy      = data.aws_iam_policy_document.replication.json
}

resource "aws_iam_role_policy_attachment" "replication" {
  role       = aws_iam_role.replication.name
  policy_arn = aws_iam_policy.replication.arn
}

Configure the cross-region replication
#

Finally, create the replication configuration:

resource "aws_s3_bucket_replication_configuration" "source_to_destination" {
  bucket = aws_s3_bucket.source.bucket
  role   = aws_iam_role.replication.arn
  region = local.source_region

  rule {
    status = "Enabled"

    delete_marker_replication {
      status = "Enabled"
    }

    filter {
      prefix = "backup/"
    }

    destination {
      bucket = aws_s3_bucket.destination.arn
    }
  }

  depends_on = [
    aws_iam_role_policy_attachment.replication,
    aws_s3_bucket_versioning.source,
    aws_s3_bucket_versioning.destination,
  ]
}

The important pieces of this configuration is to get the source and destination buckets correct. Also make sure to add a depends_on for the IAM role as well as the versioning configurations for the two buckets. If you don’t, the apply might fail.

I have added a filter that makes sure that only objects starting with backup/ will be replicated (e.g. backup/file.txt or backup/2025/05/27/test.log).


That’s it! All in all, the difference here is not substantial. When you work with more than two regions and you use many modules where you pass provider instances then you will start to notice more of a difference (for the better!)

Mattias Fjellström
Author
Mattias Fjellström
Cloud architect · Author · HashiCorp Ambassador