Check ID: s3_bucket_cross_region_replication
Provider: AWS
Service: s3
Severity: LOW
Categories: resilience
Resource type: AwsS3Bucket

What this check verifies

Amazon S3 buckets use cross-Region replication with versioning and an enabled rule that targets a destination bucket in a different AWS Region. Buckets with same-Region targets, missing destinations, or disabled versioning don’t meet this replication posture.

Risk

Single-Region storage creates an availability gap: a Regional outage, control-plane isolation, or denial of service can make data unreachable. Lack of replication raises RPO/RTO, delaying recovery and disrupting multi-Region workloads. Missing replicas also weaken data integrity during restore from corruption or deletion.

Recommendation

Enable CRR to a different Region with versioning and least-privilege roles.
  • Replicate needed prefixes and metadata
  • Consider S3 Replication Time Control for tighter RPO
  • Protect deletes via delete marker strategy and Object Lock
  • Monitor replication metrics and test DR regularly
Align with defense in depth and availability by design.

Remediation

# Enable versioning and minimal CRR to a bucket in another region
resource "aws_s3_bucket" "source" {
  bucket = "<example_resource_name>"

  versioning {                      
    enabled = true  # critical: required for replication
  }

  replication_configuration {        
    role = aws_iam_role.replication.arn  # critical: role used by S3 to replicate

    rules {
      status = "Enabled"  # critical: replication rule must be enabled
      destination {
        bucket = "arn:aws:s3:::<example_destination_bucket_name>"  # critical: destination bucket ARN in a different region
      }
    }
  }
}

resource "aws_iam_role" "replication" {
  name = "<example_resource_name>-s3-replication-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "s3.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy" "replication" {
  name = "<example_resource_name>-s3-replication-policy"
  role = aws_iam_role.replication.id
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = ["s3:ListBucket", "s3:GetReplicationConfiguration"],
        Resource = "arn:aws:s3:::${aws_s3_bucket.source.bucket}"
      },
      {
        Effect = "Allow",
        Action = [
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectVersionTagging"
        ],
        Resource = "arn:aws:s3:::${aws_s3_bucket.source.bucket}/*"
      },
      {
        Effect = "Allow",
        Action = ["s3:ReplicateObject", "s3:ReplicateDelete", "s3:ReplicateTags"],
        Resource = "arn:aws:s3:::<example_destination_bucket_name>/*"
      }
    ]
  })
}

References

Where this check fires

This check runs on every scheduled scan against your AWS account. Findings appear at Cloud Security → Findings filterable by Check ID = s3_bucket_cross_region_replication. To re-evaluate after a fix, hit Run now on the account’s schedule under Cloud Security → Schedules — the next scan re-checks this control and marks the finding fixed or keeps it persistent.
← Back to AWS checks