S3 batch replicationを利用してアカウントをまたぐS3間の既存・新規オブジェクトを全て同期する

# 概要

とあるAWSのS3バケットのオブジェクトを別のAWSアカウントのS3に同期してほしい旨の依頼を受けて対応しました。今回は別のAWSアカウントへの同期、かつ既存のオブジェクトとこれから流入してくる新規のオブジェクトを全て同期するという考慮事項がいくつかある要件だったので、自分の理解の整理も兼ねて対応した際のログを残しておきます。
当該環境ではCloudFormation（以下、CFnとします。）を利用してAWSリソースを管理しているので、コードも合わせて載せていきます。
前提として、このS3バケットには元々大量のオブジェクトが存在していたため、awscliによるs3 syncはIAMロールのセッション期間の上限の12時間を超えてしまうため利用できませんでした。

ちょうどこの相談を受けた少し前に[s3 batch replicationが既存オブジェクトの同期](https://aws.amazon.com/jp/about-aws/whats-new/2022/02/amazon-s3-batch-replication-synchronizes-existing-data-between-buckets/)に対応したのでこれを利用することにしました。

# 構成

これから順を追って作業の流れを書いていきますが、最終的な構成は以下になります。

![既存オブジェクトのレプリケーション-完成形.drawio (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/fc351194-b2ff-7c6c-a649-8ce94b3f57a3.png)

今回はS3バケットが元から存在していたので、レプリケーション元のアカウントには、レプリケーションに利用するIAMロールとバッチオペレーションで利用するIAMロールを作成し、レプリケーション先のアカウントには対象のS3バケットにレプリケーションを許可するバケットポリシーをアタッチします。

# 作業の流れ

作業の流れは以下のようになります。
ここからはレプリケーション元のS3バケットをSrc S3、レプリケーション先のものをDest S3と表記します。

[1.Src側のAWS作業：レプリケーションを行うためのIAMロールを作成する](#1.Src側のAWS作業：レプリケーションを行うためのIAMロールを作成する)
[2.Dest側のAWS作業：Dest S3に1.で作成したIAMロールからの操作を許可するバケットポリシーを設定する](#2.Dest側のAWS作業：Dest-S3に1.で作成したIAMロールからの操作を許可するバケットポリシーを設定する)
[3.Src側のAWS作業：Src S3にレプリケーションルールを設定する](#3.Src側のAWS作業：Src-S3にレプリケーションルールを設定する)
[4.Src側のAWS作業：バッチオペレーションを実行するためのIAMロールを作成する](#4.Src側のAWS作業：バッチオペレーションを実行するためのIAMロールを作成する)
[5.Src側のAWS作業：バッチオペレーションを実行する](#5.Src側のAWS作業：バッチオペレーションを実行する)

## 1.Src側のAWS作業：レプリケーションを行うためのIAMロールを作成する

まずはレプリケーションする際に利用するIAMロールを作成します。
必要な権限はAWS公式ドキュメントの[許可のセットアップページ](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/setting-repl-config-perm-overview.html)を参考に設定しました。ここではSrc S3の情報とオブジェクト情報の取得とDest S3に対するレプリケーション操作を許可しています。
CFnのテンプレートとしては以下のようになります。

```yaml
  IAMRoleReplicateSrcS3:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service:
                - s3.amazonaws.com
      Policies:
        - PolicyDocument:
            Statement:
              - Effect: 'Allow'
                Action:
                  - 's3:GetReplicationConfiguration'
                  - 's3:ListBucket'
                  - 's3:GetObjectVersionForReplication'
                  - 's3:GetObjectVersionAcl'
                  - 's3:GetObjectVersionTagging'
                Resource:
                  - !Sub 'arn:aws:s3:::${SrcBucketName}'
                  - !Sub 'arn:aws:s3:::${SrcBucketName}/*'
          PolicyName: 'access-source-s3'
        - PolicyDocument:
            Statement:
              - Effect: 'Allow'
                Action:
                  - 's3:ReplicateObject'
                  - 's3:ReplicateDelete'
                  - 's3:ReplicateTags'
                Resource:
                  - !Sub 'arn:aws:s3:::${DestBucketName}/*'
          PolicyName: 'replicate-destination-s3'
```

注意点としては、Src S3でバージョニングが有効化されている場合はバージョニング情報を取得する権限も必要になることです。権限が不足している場合は以下のように警告され、レプリケーションが開始されません。

![バージョニングの権限がない場合のreplication時の警告.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/b5d94250-041d-e1f2-fc64-0c0effdebc81.png)

この作業により、レプリケーションする際に利用するIAMロールが作成されました。
![既存オブジェクトのレプリケーション-ステップ1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/46f3c9a1-f1a9-f96e-f86e-fe88169ac12d.png)


## 2.Dest側のAWS作業：Dest S3に2で作成したIAMロールからの操作を許可するバケットポリシーを設定する

次にDest S3にバケットポリシーを追加していきます。
コードとしては以下のようになります。やっていることは、1.の手順で作成したIAMロールに対して、レプリケーションの許可とレプリケーション先のバケット情報の取得の許可になります。

```yaml
  S3BucketPolicyReplicationDestS3:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref DestBucketName
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          -
            Effect: 'Allow'
            Principal:
              AWS: !Ref IAMRoleReplicateSrcS3Arn # 1.で作成したIAMRoleのArnを指定する
            Action:
              - 's3:ReplicateDelete'
              - 's3:ReplicateObject'
              - 's3:ReplicateTags'
            Resource: !Sub 'arn:aws:s3:::${DestBucketName}/*'
          -
            Effect: 'Allow'
            Principal:
              AWS: !Ref IAMRoleReplicateSrcS3Arn # 1.で作成したIAMRoleのArnを指定する
            Action: 
              - 's3:List*'
              - 's3:GetBucketVersioning'
              - 's3:PutBucketVersioning'
            Resource: !Sub 'arn:aws:s3:::${DestBucketName}'
```

ここまででレプリケーションを行うための事前準備が整いました。現時点での構成はこのようになっています。

![既存オブジェクトのレプリケーション-ステップ2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/f0392caa-f655-6cb3-7eba-6ba32ae1b055.png)

## 3.Src側のAWS作業：Src S3にレプリケーションルールを設定する

次にSrcのS3にレプリケーションルールを設定していきます。今回は既存のSrc S3の設定にReplicationConfigurationプロパティを追加していきます。レプリケーション先として、対象のアカウントID、S3バケットのArnを指定します。

```yaml
Resources:
  S3BucketReplicationSrc:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref SrcBucketName
      #### ~~~ omit ~~~
      #### ここより下を追加する
      ReplicationConfiguration:
        - Role: !GetAtt IAMRoleReplicateSrcS3.Arn # 1.の手順で作成したIAMロールのArnを指定する
          Rules:
            - Destination:
                Account: !Ref DestinationAccountID
                Bucket: !Sub 'arn:aws:s3:::${DestBucketName}'
              Status: Enabled
```

この設定を反映すると、それ以降にSrc S3に新たに作成されたオブジェクトはDest S3にレプリケーションされます。

![既存オブジェクトのレプリケーション-ステップ3.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/fdb1ce31-aabc-fff1-7752-b61cfae5414a.png)

## 4.Src側のAWS作業：バッチオペレーションを実行するためのIAMロールを作成する

上記の手順で新規のオブジェクトが同期されるようになりましたが、既存のオブジェクトはまだ同期されていません。ここからは既存のオブジェクトを同期するための設定をしていきます。冒頭でも述べたとおり、バッチオペレーションという機能を利用することで実現可能です。
まずは、バッチオペレーションで利用するIAMロールを作成します。必要な権限は公式ドキュメントの[バッチレプリケーション用の IAM ポリシーの設定](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/s3-batch-replication-policies.html)を参考に設定しました。

```yaml
  IAMRoleBatchOperation:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service:
                - batchoperations.s3.amazonaws.com
      Policies:
        - PolicyName: BatchOperaionPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectAcl
                  - s3:GetObjectTagging
                  - s3:InitiateReplication
                  - s3:GetReplicationConfiguration
                  - s3:PutInventoryConfiguration
                Resource:
                  - !Sub 'arn:aws:s3:::${SrcBucketName}'
                  - !Sub 'arn:aws:s3:::${SrcBucketName}/*'
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:PutObjectAcl
                  - s3:PutObjectTagging
                  - s3:PutObjectLegalHold
                  - s3:PutObjectRetention
                  - s3:GetBucketObjectLockConfiguration
                Resource:
                  - !Sub 'arn:aws:s3:::${DestBucketName}/*'
```

正直なところ、私はこのバッチオペレーション用のIAMロールが必要ということに気づけずにハマりました。新規のオブジェクトをレプリケーションするのも、既存のオブジェクトをレプリケーションするのも権限的には変わらないだろうと思い込み、バッチレプリーションを実行する際のロールに1.で作成したロールを指定していたところ、 `Error occurred when preparing manifest: Failed to assume role.`というエラーが発生しました。
![バッチオペレーションが失敗した.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/613a0fd0-c207-9c09-242d-0164596a1875.png)

冷静にエラー内容を見ると、権限不足ではなく、assume roleができないことで怒られています。そのため、利用しようとしたサービスから、指定したIAMロールにassume roleできないのはなぜかという点で、もしかしたら別のロールが必要なのかも？とアタリをつけて調べて、上記のページを発見して解決することができました。
確かにバッチオペレーション用のロールでは、`batchoperations.s3.amazonaws.com`にassume roleを許可しているのに対し、レプリケーション用のロールは `s3.amazonaws.com` に許可しているので、全て調べ終わった後の今なら納得がいきます。

なにはともあれバッチオペレーション実行の準備が整いました。
![既存オブジェクトのレプリケーション-ステップ４.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/4eebce69-cef6-6af0-bb51-eaee18260dfd.png)


## 5.Src側のAWS作業：バッチオペレーションを実行する

ここからはAWSコンソール上で作業していきます。
Src S3のレプリケーションの設定を表示すると「レプリケーションルール」の画面に遷移するので、`レプリケーションジョブを作成します`を押下します。S3サービスのトップ画面にあるメニューからもバッチオペレーションの操作画面に飛べるのですが、こちらから飛ぶとレプリケーションルールに設定したバケットの情報が選択された状態で進めるので、設定が少し楽になります。
![レプリケーションジョブの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/51ddd519-5277-bb3f-3910-151f3af99f76.png)

次の「リージョンとマニフェストを選択」では、本来であればジョブを作成するリージョンとソースバケット、フィルター条件の指定が必要になりますが、レプリケーションルールの画面からジョブを作成している場合は、これらの項目は入力済みなのでスキップできます。
![リージョンとマニフェストを選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/20c37218-4031-3de8-6aaa-19b50f89a82f.png)

「オペレーションを選択」の画面に遷移します。`レプリケート`を選択して`次へ`を押下します。私の環境ではレプリケート以外は非アクティブとなっていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/4e4e46a2-8142-036c-0d97-17b6e532d2c3.png)

次に追加のオプションを設定します。`アクセス許可`のメニューから`既存のIAMロールから選択` を選び、IAMロールのプルダウンメニューから、`手順4.で作成したIAMロール`を選択し、`次へ`を押下します。確認画面が表示されるので問題なければ完了します。
![スクリーンショット 2022-12-09 14.51.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/15718316-4dcf-c988-030f-6a6f4b203943.png)

ジョブが作成されます。初期のステータスは準備中です。準備が完了すると、ステータスが「実行のための確認待ち」に変わるため、対象のジョブを選択して「ジョブを実行」を押下します。このオペレーションが忘れがちで、行わないといつまで経ってもジョブが開始されないので、忘れずに実行します。実行後はステータスが「アクティブ」に変わり、「完了済み」になったら全てのオブジェクトのレプリケーションが完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/1e299bb7-bd0b-0e65-d2ae-77e379b7dd27.png)

# さいごに

今回は割と要件が色々乗っかったS3のレプリケーションを試しました。既存オブジェクトの同期にはバッチオペレーションの他、レプリケーションルールからも既存オブジェクトを同期できそうな設定があったのですが、うまく動きませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316261/e3550329-cee3-d999-5530-93ee46bf9227.png)

試行錯誤していたので設定に誤りがあったのか、[このドキュメント](https://aws.amazon.com/jp/blogs/storage/replicating-existing-objects-between-s3-buckets/)ではレプリケーションが有効化されてから実際にレプリケーションが開始されるまでに、最大で48時間かかると言及されているので、開始前に設定を変えてしまったかは定かではありません。
> Note: Unlike the replication of newly uploaded object, it takes up to 48 hours for the replication to start after the replication rule is set up for existing objects.

ただ、１回の検証サイクルで48時間使うことを許容できないケースもありますし、[GithubのIssue](https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/909)でも言及されている通り、上位互換のバッチオペレーションがリリースされてからは開発も停滞気味なので、既存オブジェクトの同期にわざわざレプリケーションルールを利用する必要はないかなという感想をもちました。