private renderReplicationConfiguration()

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,
        };
      }),
    };
  }