in packages/aws-cdk-lib/aws-s3/lib/bucket.ts [2790:2927]
private renderReplicationConfiguration(props: BucketProps): CfnBucket.ReplicationConfigurationProperty | undefined {
const replicationRulesIsEmpty = !props.replicationRules || props.replicationRules.length === 0;
if (replicationRulesIsEmpty && props.replicationRole) {
throw new ValidationError('cannot specify replicationRole when replicationRules is empty', this);
}
if (replicationRulesIsEmpty) {
return undefined;
}
if (!props.versioned) {
throw new ValidationError('Replication rules require versioning to be enabled on the bucket', this);
}
if (props.replicationRules.length > 1 && props.replicationRules.some(rule => rule.priority === undefined)) {
throw new ValidationError('\'priority\' must be specified for all replication rules when there are multiple rules', this);
}
props.replicationRules.forEach(rule => {
if (rule.replicationTimeControl && !rule.metrics) {
throw new ValidationError('\'replicationTimeControlMetrics\' must be enabled when \'replicationTimeControl\' is enabled.', this);
}
if (rule.deleteMarkerReplication && rule.filter?.tags) {
throw new ValidationError('tag filter cannot be specified when \'deleteMarkerReplication\' is enabled.', this);
}
});
const destinationBuckets = props.replicationRules.map(rule => rule.destination);
const kmsKeys = props.replicationRules.map(rule => rule.kmsKey).filter(kmsKey => kmsKey !== undefined) as kms.IKey[];
let replicationRole: iam.IRole;
if (!props.replicationRole) {
replicationRole = new iam.Role(this, 'ReplicationRole', {
assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'),
roleName: FeatureFlags.of(this).isEnabled(cxapi.SET_UNIQUE_REPLICATION_ROLE_NAME) ? PhysicalName.GENERATE_IF_NEEDED : 'CDKReplicationRole',
});
// add permissions to the role
// @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/setting-repl-config-perm-overview.html
replicationRole.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['s3:GetReplicationConfiguration', 's3:ListBucket'],
resources: [Lazy.string({ produce: () => this.bucketArn })],
effect: iam.Effect.ALLOW,
}));
replicationRole.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['s3:GetObjectVersionForReplication', 's3:GetObjectVersionAcl', 's3:GetObjectVersionTagging'],
resources: [Lazy.string({ produce: () => this.arnForObjects('*') })],
effect: iam.Effect.ALLOW,
}));
if (destinationBuckets.length > 0) {
replicationRole.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['s3:ReplicateObject', 's3:ReplicateDelete', 's3:ReplicateTags', 's3:ObjectOwnerOverrideToBucketOwner'],
resources: destinationBuckets.map(bucket => bucket.arnForObjects('*')),
effect: iam.Effect.ALLOW,
}));
}
kmsKeys.forEach(kmsKey => {
kmsKey.grantEncrypt(replicationRole);
});
// If KMS key encryption is enabled on the source bucket, configure the decrypt permissions.
this.encryptionKey?.grantDecrypt(replicationRole);
} else {
replicationRole = props.replicationRole;
}
return {
role: replicationRole.roleArn,
rules: props.replicationRules.map((rule) => {
const sourceSelectionCriteria = (rule.replicaModifications !== undefined || rule.sseKmsEncryptedObjects !== undefined) ? {
replicaModifications: rule.replicaModifications !== undefined ? {
status: rule.replicaModifications ? 'Enabled' : 'Disabled',
} : undefined,
sseKmsEncryptedObjects: rule.sseKmsEncryptedObjects !== undefined ? {
status: rule.sseKmsEncryptedObjects ? 'Enabled' : 'Disabled',
} : undefined,
} : undefined;
// Whether to configure filter settings by And property.
const isAndFilter = rule.filter?.tags && rule.filter.tags.length > 0;
// To avoid deploy error when there are multiple replication rules with undefined prefix,
// CDK set the prefix to an empty string if it is undefined.
const prefix = rule.filter?.prefix ?? '';
const filter = isAndFilter ? {
and: {
prefix,
tagFilters: rule.filter?.tags,
},
} : {
prefix,
};
const sourceAccount = Stack.of(this).account;
const destinationAccount = rule.destination.env.account;
const isCrossAccount = sourceAccount !== destinationAccount;
if (isCrossAccount) {
Annotations.of(this).addInfo('For Cross-account S3 replication, ensure to set up permissions on destination bucket using method addReplicationPolicy() ');
} else if (rule.accessControlTransition) {
throw new ValidationError('accessControlTranslation is only supported for cross-account replication', this);
}
return {
id: rule.id,
priority: rule.priority,
status: 'Enabled',
destination: {
bucket: rule.destination.bucketArn,
account: isCrossAccount ? destinationAccount : undefined,
storageClass: rule.storageClass?.toString(),
accessControlTranslation: rule.accessControlTransition ? {
owner: 'Destination',
} : undefined,
encryptionConfiguration: rule.kmsKey ? {
replicaKmsKeyId: rule.kmsKey.keyArn,
} : undefined,
replicationTime: rule.replicationTimeControl !== undefined ? {
status: 'Enabled',
time: {
minutes: rule.replicationTimeControl.minutes,
},
} : undefined,
metrics: rule.metrics !== undefined ? {
status: 'Enabled',
eventThreshold: {
minutes: rule.metrics.minutes,
},
} : undefined,
},
filter,
// To avoid deploy error when there are multiple replication rules with undefined deleteMarkerReplication,
// CDK explicitly set the deleteMarkerReplication if it is undefined.
deleteMarkerReplication: {
status: rule.deleteMarkerReplication ? 'Enabled' : 'Disabled',
},
sourceSelectionCriteria,
};
}),
};
}