Back to Checks
S3 bucket has cross-region replication configured to a bucket in a different region
s3_bucket_cross_region_replication
Service: s3Severity: low
OfficialBuilt-inVerified
Categories:resilience
Resource Type:AwsS3Bucket
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.
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks s3_bucket_cross_region_replicationRecommendation
Enable CRR to a different Region with versioning and least-privilege roles.
- Replicate needed prefixes and metadata
- Consider
S3 Replication Time Controlfor tighter RPO - Protect deletes via
delete markerstrategy and Object Lock - Monitor replication metrics and test DR regularly
Align with defense in depth and availability by design.
Remediation
Terraform
# 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>/*"
}
]
})
}NativeIaC
# CloudFormation: Enable versioning and CRR to a bucket in another region
Resources:
ExampleBucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled # critical: enables versioning (required for replication)
ReplicationConfiguration: # critical: enables cross-Region replication
Role: !GetAtt ReplicationRole.Arn
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
ReplicationRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: s3.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: s3-replication-minimal
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetReplicationConfiguration
Resource: !Sub arn:aws:s3:::${ExampleBucket}
- Effect: Allow
Action:
- s3:GetObjectVersionForReplication
- s3:GetObjectVersionAcl
- s3:GetObjectVersionTagging
Resource: !Sub arn:aws:s3:::${ExampleBucket}/*
- Effect: Allow
Action:
- s3:ReplicateObject
- s3:ReplicateDelete
- s3:ReplicateTags
Resource: arn:aws:s3:::<example_destination_bucket_name>/*Other
1In the S3 console, open the source bucket
2Go to Properties > Bucket Versioning and click Enable
3Go to Management > Replication rules > Create replication rule
4Scope: Apply to all objects in the bucket
5Destination: Select a bucket in a different AWS Region
6Status: Ensure Enabled is selected
7IAM role: Choose Create new role (recommended)
8Save the rule
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.s3.s3_client import s3_client
class s3_bucket_cross_region_replication(Check):
"""Ensure S3 buckets replicate to a bucket in a different region.
- PASS: At least one enabled replication rule targets a bucket in another region.
- FAIL: Versioning is disabled, no enabled rule exists, or every resolvable
destination is in the same region.
- MANUAL: The versioning or replication configuration could not be retrieved
(missing permissions), or a destination bucket is outside the audited
account/scope so its region cannot be determined.
"""
def execute(self) -> list[Check_Report_AWS]:
findings = []
for bucket in s3_client.buckets.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=bucket)
if not bucket.versioning_retrieved or not bucket.replication_retrieved:
report.status = "MANUAL"
report.status_extended = f"Cannot evaluate cross region replication for S3 Bucket {bucket.name}: the versioning or replication configuration could not be retrieved. Verify that the scanning credentials are allowed to call s3:GetBucketVersioning and s3:GetReplicationConfiguration."
findings.append(report)
continue
report.status = "FAIL"
report.status_extended = f"S3 Bucket {bucket.name} does not have correct cross region replication configuration."
unresolvable_report = None
same_region_report = None
if bucket.replication_rules:
for rule in bucket.replication_rules:
if (
bucket.versioning
and rule.status == "Enabled"
and rule.destination
):
if rule.destination not in s3_client.buckets:
unresolvable_report = f"S3 Bucket {bucket.name} has cross region replication rule {rule.id} in bucket {rule.destination.split(':')[-1]} which is out of Prowler's scope; verify manually that the destination bucket is in a different region."
else:
destination_bucket = s3_client.buckets[rule.destination]
if destination_bucket.region != bucket.region:
report.status = "PASS"
report.status_extended = f"S3 Bucket {bucket.name} has cross region replication rule {rule.id} in bucket {destination_bucket.name} located in region {destination_bucket.region}."
break
else:
same_region_report = f"S3 Bucket {bucket.name} has cross region replication rule {rule.id} in bucket {destination_bucket.name} located in the same region."
# Precedence: PASS > MANUAL (unresolvable destination) > FAIL
if report.status != "PASS":
if unresolvable_report:
report.status = "MANUAL"
report.status_extended = unresolvable_report
elif same_region_report:
report.status_extended = same_region_report
findings.append(report)
return findings