S3 Cross-Account Replication With KMS Encryption and IAM Roles

Ned
Ned Cloud Engineer
· Updated · 6 min read
S3 Cross-Account Replication With KMS Encryption and IAM Roles

The Problem

You need every object written to a bucket in account A to appear in a bucket in account B, in a different Region, both sides encrypted with customer-managed KMS keys. Account B owns the destination bucket and its KMS key. Account A owns the replication role. Nothing works until every piece of policy on both sides agrees.

The Solution

Set up S3 replication on the source bucket with a rule that targets the destination bucket in account B. Wire up four things correctly:

  1. The IAM replication role in account A
  2. The source KMS key policy in account A
  3. The destination bucket policy in account B
  4. The destination KMS key policy in account B

Miss any one and replication silently fails.

How It Works

Prerequisites

  • Versioning enabled on both buckets, since replication requires it
  • Both buckets exist in their own accounts and Regions
  • A KMS key in each account and each Region

The Replication Role (Account A)

Trust policy. S3 assumes this role to perform the replication:

1
2
3
4
5
6
7
8
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "s3.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}

Permissions policy. Read from source, write to destination, decrypt source KMS, encrypt with destination KMS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetReplicationConfiguration",
        "s3:ListBucket",
        "s3:GetObjectVersionForReplication",
        "s3:GetObjectVersionAcl",
        "s3:GetObjectVersionTagging"
      ],
      "Resource": [
        "arn:aws:s3:::source-hadzimahmutovic",
        "arn:aws:s3:::source-hadzimahmutovic/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ReplicateObject",
        "s3:ReplicateDelete",
        "s3:ReplicateTags"
      ],
      "Resource": "arn:aws:s3:::dest-clearview-team/*"
    },
    {
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-1:111111111111:key/source-key-id",
      "Condition": {
        "StringLike": {
          "kms:ViaService": "s3.eu-west-1.amazonaws.com",
          "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::source-hadzimahmutovic/*"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": "kms:Encrypt",
      "Resource": "arn:aws:kms:us-east-1:222222222222:key/dest-key-id",
      "Condition": {
        "StringLike": {
          "kms:ViaService": "s3.us-east-1.amazonaws.com",
          "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::dest-clearview-team/*"
        }
      }
    }
  ]
}

The Destination Bucket Policy (Account B)

Account B must explicitly grant the replication role from account A permission to write replicas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::111111111111:role/S3ReplicationRole"
    },
    "Action": [
      "s3:ReplicateObject",
      "s3:ReplicateDelete",
      "s3:ReplicateTags",
      "s3:ObjectOwnerOverrideToBucketOwner"
    ],
    "Resource": "arn:aws:s3:::dest-clearview-team/*"
  }]
}

The Destination KMS Key Policy (Account B)

Grant kms:Encrypt (and helper actions) to the replication role from account A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "Sid": "AllowSourceReplicationRole",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::111111111111:role/S3ReplicationRole"
  },
  "Action": [
    "kms:Encrypt",
    "kms:GenerateDataKey",
    "kms:ReEncrypt*",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}

The Source KMS Key Policy (Account A)

Grant kms:Decrypt to the replication role. If the role is in the same account, an IAM policy is usually enough, but adding an explicit key policy statement removes doubt:

1
2
3
4
5
6
7
8
9
{
  "Sid": "AllowReplicationRoleDecrypt",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::111111111111:role/S3ReplicationRole"
  },
  "Action": "kms:Decrypt",
  "Resource": "*"
}

The Replication Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
aws s3api put-bucket-replication \
  --bucket source-hadzimahmutovic \
  --replication-configuration '{
    "Role": "arn:aws:iam::111111111111:role/S3ReplicationRole",
    "Rules": [{
      "ID": "replicate-to-b",
      "Status": "Enabled",
      "Priority": 1,
      "Filter": { "Prefix": "" },
      "DeleteMarkerReplication": { "Status": "Enabled" },
      "Destination": {
        "Bucket": "arn:aws:s3:::dest-clearview-team",
        "Account": "222222222222",
        "AccessControlTranslation": { "Owner": "Destination" },
        "EncryptionConfiguration": {
          "ReplicaKmsKeyID": "arn:aws:kms:us-east-1:222222222222:key/dest-key-id"
        }
      },
      "SourceSelectionCriteria": {
        "SseKmsEncryptedObjects": { "Status": "Enabled" }
      }
    }]
  }'

AccessControlTranslation transfers ownership to account B. Without it, account B owns the bucket but account A owns the replicated objects, which breaks downstream access.

Verifying Replication

Upload a test object and check the replication status:

1
2
3
4
aws s3api head-object \
  --bucket source-hadzimahmutovic \
  --key test.txt \
  --query ReplicationStatus

Values: PENDING, COMPLETED, FAILED. Enable S3 Replication Time Control (RTC) if you need a 15-minute SLA and per-object metrics in CloudWatch.

Why Not the Alternatives?

Same-account replication only — Solves nothing for the cross-account requirement. The destination bucket owner and the source bucket owner must be able to enforce policy independently.

Client-side script that copies objects — Fragile, no atomic ordering, no replication metrics, no delete marker handling, no retry on transient failures.

AWS DataSync between the two buckets — Batch and scheduled, not continuous. Not the right tool for real-time replication.

Multi-Region access points without replication — MRAPs route requests, they do not create replicas. You still need replication underneath if you want physical copies.

Key Takeaways

  • Cross-account replication with KMS needs four aligned policies: role trust, role permissions, destination bucket policy, and both KMS key policies
  • Use AccessControlTranslation to give the destination account ownership of the replicas
  • Enable SseKmsEncryptedObjects in SourceSelectionCriteria so KMS-encrypted objects actually replicate
  • Add S3 Replication Time Control for a 15-minute replication SLA with CloudWatch metrics
  • Check ReplicationStatus on the source object to confirm the flow works end to end
Rating:
Share
Previous AWS Transfer Family: A Managed SFTP Endpoint Backed by S3 for Enterprise File Transfers