diff --git a/multiregion-s3/README.md b/multiregion-s3/README.md new file mode 100644 index 0000000..8c460a5 --- /dev/null +++ b/multiregion-s3/README.md @@ -0,0 +1,2 @@ +# Multiregion S3 bucket +Creates a bi-directionally replicated S3 bucket between 2 regions with the same suffix. \ No newline at end of file diff --git a/multiregion-s3/main.tf b/multiregion-s3/main.tf new file mode 100644 index 0000000..9a11fcf --- /dev/null +++ b/multiregion-s3/main.tf @@ -0,0 +1,144 @@ +# S3 Buckets +resource "aws_s3_bucket" "buckets" { + for_each = toset([var.Region1, var.Region2]) + region = each.value + bucket = "${var.BucketPrefix}-${each.value}" +} + +# Enable versioning (required for replication) +resource "aws_s3_bucket_versioning" "versioning" { + for_each = toset([var.Region1, var.Region2]) + region = each.value + bucket = aws_s3_bucket.buckets[each.value].id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_iam_role" "replication" { + name = "${var.BucketPrefix}-replication-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "s3.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_policy" "replication" { + name = "${var.BucketPrefix}-replication-policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "s3:GetReplicationConfiguration", + "s3:ListBucket" + ] + Effect = "Allow" + Resource = [ + for bucket in aws_s3_bucket.buckets : bucket.arn + ] + }, + { + Action = [ + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionTagging" + ] + Effect = "Allow" + Resource = [ + for bucket in aws_s3_bucket.buckets : "${bucket.arn}/*" + ] + }, + { + Action = [ + "s3:ReplicateObject", + "s3:ReplicateDelete", + "s3:ReplicateTags" + ] + Effect = "Allow" + Resource = [ + for bucket in aws_s3_bucket.buckets : "${bucket.arn}/*" + ] + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "replication" { + role = aws_iam_role.replication.name + policy_arn = aws_iam_policy.replication.arn +} + +# Replication configuration: Region1 -> Region2 +resource "aws_s3_bucket_replication_configuration" "region1_to_region2" { + region = var.Region1 + role = aws_iam_role.replication.arn + bucket = aws_s3_bucket.buckets[var.Region1].id + + rule { + id = "replicate-to-${var.Region2}" + status = "Enabled" + priority = 1 + + filter {} + + destination { + bucket = aws_s3_bucket.buckets[var.Region2].arn + storage_class = "STANDARD" + } + + delete_marker_replication { + status = "Enabled" + } + } + + depends_on = [aws_s3_bucket_versioning.versioning] +} + +# Replication configuration: Region2 -> Region1 +resource "aws_s3_bucket_replication_configuration" "region2_to_region1" { + region = var.Region2 + role = aws_iam_role.replication.arn + bucket = aws_s3_bucket.buckets[var.Region2].id + + rule { + id = "replicate-to-${var.Region1}" + status = "Enabled" + priority = 1 + + filter {} + + destination { + bucket = aws_s3_bucket.buckets[var.Region1].arn + storage_class = "STANDARD" + } + + delete_marker_replication { + status = "Enabled" + } + } + + depends_on = [aws_s3_bucket_versioning.versioning] +} + +output "buckets_info" { + description = "Map of bucket information by region" + value = { + for region, bucket in aws_s3_bucket.buckets : region => { + arn = bucket.arn + bucket_regional_domain_name = bucket.bucket_regional_domain_name + bucket_domain_name = bucket.bucket_domain_name + id = bucket.id + } + } +} diff --git a/multiregion-s3/variables.tf b/multiregion-s3/variables.tf new file mode 100644 index 0000000..81b6498 --- /dev/null +++ b/multiregion-s3/variables.tf @@ -0,0 +1,28 @@ +data "aws_regions" "current" { + all_regions = true +} + +variable "Region1" { + type = string + description = "Region for bucket 1" + + validation { + condition = contains(data.aws_regions.current.names, var.Region1) + error_message = "Region1 must be a valid AWS region. Available regions: ${join(", ", data.aws_regions.current.names)}" + } +} + +variable "Region2" { + type = string + description = "Region for bucket 2" + + validation { + condition = contains(data.aws_regions.current.names, var.Region2) + error_message = "Region1 must be a valid AWS region. Available regions: ${join(", ", data.aws_regions.current.names)}" + } +} + +variable "BucketPrefix" { + type = string + description = "Prefix for the bucket. The created bucket names will be in the form of BucketPrefix-Region." +}