DEV Community

Cover image for Implementing AWS S3 Cross-Account Replication.
Gerson Morales
Gerson Morales

Posted on

Implementing AWS S3 Cross-Account Replication.

The purpose of this post is to offer a detailed, step-by-step guide to setting up an example of S3 Cross-Account Replication between buckets.

Requirements

1- Two AWS accounts (Account A and Account B).
2- Two Amazon S3 buckets (one in each account).
3- Appropriate IAM policies for access control.
4- IAM roles with necessary trust relationships and permissions.
5- Terraform for infrastructure as code deployment.

Scenario

We have two AWS accounts: Account A (111111111111) and Account B (222222222222). Our objective is to replicate objects from the source bucket, gerx24-source-bucket (in Account A), to the destination bucket, gerx24-destination-bucket (in Account B), using Amazon S3 Cross-Account Replication.

Additionally, we want to apply a lifecycle policy to buckets to automatically expire replicated objects after 10 days.

Folder Structure

Image description

Terraform files [Module]

Reusable Terraform module for provisioning Amazon S3 buckets.

main.tf

module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "4.9.0"
  bucket  = var.bucket_name

  control_object_ownership = true
  object_ownership         = "BucketOwnerPreferred"

  acl = "private"

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
  versioning = {
    status = "Enabled"
  }

  lifecycle_rule = var.lifecycle_rules

  server_side_encryption_configuration = {
    rule = {
      apply_server_side_encryption_by_default = {
        sse_algorithm = "AES256"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

outputs.tf

output "s3_bucket_id" {
  description = "Bucket ID"
  value       = module.s3_bucket.s3_bucket_id
}
output "s3_bucket_arn" {
  description = "Bucket ARN"
  value       = module.s3_bucket.s3_bucket_arn
}
Enter fullscreen mode Exit fullscreen mode

variables.tf

variable "environment" {
  default     = null
  description = "Environment"
  type        = string
}

variable "bucket_name" {
  default     = null
  description = "Bucket name"
  type        = string
}

variable "lifecycle_rules" {
  default     = {}
  description = "Lifecycle rules object"
  type        = any
}
Enter fullscreen mode Exit fullscreen mode

Terraform files [Source Bucket]

S3 Cross-Account Replication source bucket.

1- Creates bucket [gerx24-source-bucket].
2- Creates a Role replication_role [source-replication-role].
3- Creates replication_policy [source-replication-policy].
4- Creates replication_bucket_config and bind it with [gerx24-source-bucket] bucket.
5- Includes in [gerx24-source-bucket] a lifecycle_rules to expire files after 10 days.

main.tf

module "source_replication_bucket" {
  source = "../module/bucket"
  environment        = "dev"
  bucket_name        = "gerx24-source-bucket"
  lifecycle_rules = [
    {
      id      = "Delete files older than 10 days"
      enabled = true
      expiration = {
        days = 10
      }
    }
  ]
}

locals {
  source_replication_bucket_arn  = module.source_replication_bucket.s3_bucket_id
  bucket_name                    = module.source_replication_bucket.s3_bucket_arn
  destination_bucket_arn         = "arn:aws:s3:::gerx24-destination-bucket"
  source_replication_policy_name = "source-replication-policy"
  source_replication_role_name   = "source-replication-role"


  common_tags = {
    environment = "dev"
    maintainer  = "gerx24"
  }
}

resource "aws_iam_role" "replication_role" {
  name = local.source_replication_role_name

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Principal = {
          Service = "s3.amazonaws.com"
        },
        Effect = "Allow",
        Sid = ""
      }
    ]
  })

  tags = local.common_tags
}

resource "aws_iam_policy" "replication_policy" {
  name = local.source_replication_policy_name

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = [
          "s3:GetReplicationConfiguration",
          "s3:ListBucket"
        ],
        Resource = [
          "${local.source_replication_bucket_arn}/*",
          "${local.source_replication_bucket_arn}",
        ]
      },
      {
        Effect = "Allow",
        Action = [
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectVersionTagging"
        ],
        Resource = [
          "${local.source_replication_bucket_arn}/*",
          "${local.source_replication_bucket_arn}"
        ]
      },
      {
        Effect = "Allow",
        Action = [
          "s3:ReplicateObject",
          "s3:ReplicateDelete",
          "s3:ReplicateTags",
          "s3:ObjectOwnerOverrideToBucketOwner"
        ],
        Resource = [
          "${local.destination_bucket_arn}/*",
          "${local.destination_bucket_arn}"
        ]
      }
    ]
  })
}

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

resource "aws_s3_bucket_replication_configuration" "replication_bucket_config" {
  depends_on = [module.source_replication_bucket]

  role   = aws_iam_role.replication_role.arn
  bucket = local.bucket_name

  rule {
    id = "cross-replication"
    delete_marker_replication {
      status = "Disabled"
    }
    source_selection_criteria {
      replica_modifications {
        status = "Enabled"
      }

    }
    filter {
      prefix = ""
    }

    status = "Enabled"

    destination {
      bucket        = local.destination_bucket_arn
      storage_class = "STANDARD"
      access_control_translation {
        owner = "Destination"
      }
      account = "222222222222"

      metrics {
        status = "Enabled"

        event_threshold {
          minutes = 15
        }
      }
      replication_time {
        status = "Enabled"

        time {
          minutes = 15
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

provider.tf

provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = {
      terraform   = "true"
      application = "replication"
      environment = "dev"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Terraform files [Destination Bucket]

S3 Cross-Account Replication destination bucket.

1- Creates bucket [gerx24-destination-bucket].
2- Creates destination_bucket_replication_policy and attach it to [gerx24-destination-bucket] bucket.

main.tf

module "destination_replication_bucket" {
  source = "../module/bucket"

  bucket_name = "gerx24-destination-bucket"
  environment = "dev"
  lifecycle_rules = [
    {
      id      = "Delete files older than 10 days"
      enabled = true
      expiration = {
        days = 10
      }
    }
  ]
}

locals {
  destination_bucket_name = module.destination_replication_bucket.s3_bucket_id
  source_replication_role = "arn:aws:iam::111111111111:role/source-replication-role"
}

resource "aws_s3_bucket_policy" "destination_bucket_replication_policy" {
  bucket = local.destination_bucket_name

  policy = jsonencode({
    Version = "2012-10-17"
    Id      = ""
    Statement = [
      {
        Sid    = "AllowReplicationfromSourceBucket"
        Effect = "Allow"
        Principal = {
          AWS = local.source_replication_role
        }
        Action = [
          "s3:ReplicateObject",
          "s3:ReplicateDelete",
          "s3:ReplicateTags",
          "s3:ObjectOwnerOverrideToBucketOwner",
          "s3:List*",
          "s3:GetBucketVersioning",
          "s3:PutBucketVersioning"
        ]
        Resource = [
          "arn:aws:s3:::${local.destination_bucket_name}/*",
          "arn:aws:s3:::${local.destination_bucket_name}"
        ]
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

provider.tf

provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = {
      terraform   = "true"
      application = "replication"
      environment = "dev"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting doc

Top comments (0)