S3のレプリケーション（同一AWSアカウント）

## はじめに
DevOpsの一環でCICDを構築した際、レプリケーション先で[レプリケート後にDeployを実行させる仕組みを構築した](https://qiita.com/yuta-katayama-23/items/64a5b3a7b7c3f9b76e2f)が、自分ではレプリケーションを設定した事がなかったので試しにやってみた

また、今回レプリケーションの設定をやる中でIAMロール？サービスロール？といった言葉出てきて、それぞれのロールの違いって何？となったのでそれについても調べたりして[自分なりに理解を深めてみた](https://qiita.com/yuta-katayama-23/items/0606e8d590968e43fd27)を参照）
※一応自分の理解を確かめるという意味でも、ここではあえてIAMロールとサービスロールの2パターンでレプリケーションをやってみた

・参考：[チュートリアル: レプリケーションの設定](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-example-walkthroughs.html)
・参考：[同じアカウントが所有するレプリケート元バケットとレプリケート先バケットのレプリケーションの設定](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-walkthrough1.html)

## IAMロール版
### IAMロールの作成
レプリケーションは、S3というAWSサービスがユーザに代わってやる事なのでS3にロールを渡す必要があり、IAMロール版ではIAMロールをS3に渡す事になるのでまずはそのIAMロールを作成する

ここではManagement Consoleから作成した（[サービスロール版](#サービスロール版)ではAWS CLIコマンドで作成）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/b29bccb0-eaac-a653-fbc7-11fe7fa3c9c0.png)
※`パス`の部分が`/`になっているように、これはサービスロールではなくIAMロール

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetReplicationConfiguration"
            ],
            "Resource": "arn:aws:s3:::repli-source"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:GetObjectVersionTagging",
                "s3:GetObjectVersionAcl",
                "s3:GetObjectVersionForReplication"
            ],
            "Resource": "arn:aws:s3:::repli-source/*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": [
                "s3:ReplicateObject",
                "s3:ObjectOwnerOverrideToBucketOwner",
                "s3:ReplicateTags",
                "s3:ReplicateDelete"
            ],
            "Resource": "arn:aws:s3:::repli-destinat/*"
        }
    ]
}
```
| action名 | 説明 |
|---|---|
| s3:ListBucket | Amazon S3バケット内のオブジェクトの一部またはすべてを一覧表示する権限。レプリケーションするオブジェクトの対象を把握するのに必要。 |
| s3:GetReplicationConfiguration | AmazonS3バケットに設定されたレプリケーション構成情報を取得する権限。 |
| s3:GetObjectVersionTagging | オブジェクトの特定のバージョンに設定されたタグを返す権限。 |
| s3:GetObjectVersionAcl | 特定のオブジェクトバージョンのアクセス制御リスト（ACL）を返す権限。 |
| s3:GetObjectVersionForReplication | 暗号化されていないオブジェクトとSSE-S3またはSSE-KMSで暗号化されたオブジェクトの両方を複製する権限 |
| s3:ReplicateObject | オブジェクトとオブジェクトタグを宛先バケットに複製する権限。 |
| s3:ObjectOwnerOverrideToBucketOwner | レプリカ（レプリケーションされたオブジェクト）の所有権を変更する権限。<br>・参考：[レプリケート先バケットポリシーへのレプリカの所有権を変更するアクセス許可の追加](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-change-owner.html#repl-ownership-accept-ownership-b-policy) |
| s3:ReplicateTags | オブジェクトタグをレプリケーション先のバケットに複製する権限。 |
| s3:ReplicateDelete | 削除マーカーをレプリケーション先のバケットに複製する権限。 |

・参考：[AWS のサービス用ロールの作成 (コンソール)](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console)
・参考：[許可のセットアップ](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/setting-repl-config-perm-overview.html)

### S3（レプリケーション元）の設定
IAMロールに上記[IAMロールの作成](#iamロールの作成)で作成したロールを指定する事で、S3にレプリケートを行う権限を付与できる
※`レプリケーション時間のコントロール (RTC)`については追加で課金されるので注意が必要だが、検証する上で時間がかかり過ぎるのも困るので☑している（私はテキストファイル1kbを5、6回レプリケーションをやったが特に課金はされていなかった）

| #1 | #2 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/6409e955-29eb-360f-5947-54853a3694c6.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/10c6f5a1-92ae-88aa-b1ec-817366fd9321.png) |

### S3（レプリケーション先）の設定
今回は同一AWSアカウントのバケット間でのレプリケーションなので設定不要

### 実際にレプリケーションを確認してみる
以下のようにそれぞれレプリケーションが成功しているか？がステータスで分かる

| レプリケーション元 | レプリケーション先 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/471aa50f-0e15-1383-886b-48a06ce54791.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/931e9658-7836-a682-b25f-92fbeedaf346.png) |

## サービスロール版
### サービスロールの作成
サービスロールはAWS CLIコマンドからしか作れない（と思っている）
ので以下のようなコマンドでサービスロールを作成する

※`--path /service-role/`のようにパスを指定する事でサービスロールになる（サービスロールか？は識別子でしかなくロールの本質的な部分はIAMロールもサービスロールも同じでそれぞれ便宜上の区分で名前が違う。詳細は[IAMロールとAWSサービスロールの違いって何？（自分なりの理解）](https://qiita.com/yuta-katayama-23/items/0606e8d590968e43fd27)を参照。）

```bash
aws iam create-role \
--role-name replication-service-role \
--assume-role-policy-document file://replication-service-role-trust-policy.json \
--path /service-role/

aws iam put-role-policy \
--role-name replication-service-role \
--policy-name replication \
--policy-document file://replication-service-role-inline-policy.json
```
```json:replication-service-role-trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "s3.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

※`replication-service-role-inline-policy.json`の中身は[IAMロールの作成](#iamロールの作成)の項のJSONとほぼ同じで、`"Resource"`のバケット名の部分だけ変更したもの（`repli-source-cli`, `repli-destinat-cli`）

AWS CLIコマンドのリファレンスは以下

- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-role.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/put-role-policy.html

・参考：[IAM 識別子　IAM ARN](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns)
・参考：[IAM ロールの作成](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/setting-repl-config-perm-overview.html#setting-repl-config-same-acctowner)

### S3バケットの作成
単にバケットを作成しただけだと、デフォルトでパブリックアクセス制限がオフになっているので、作成後にパブリックアクセス制限をオンにする

```bash
aws s3api create-bucket --bucket repli-source-cli --create-bucket-configuration LocationConstraint=ap-northeast-1
aws s3api create-bucket --bucket repli-destinat-cli --create-bucket-configuration LocationConstraint=ap-northeast-1

aws s3api put-public-access-block 
--bucket repli-source-cli \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
aws s3api put-public-access-block \
--bucket repli-destinat-cli \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
```

AWS CLIコマンドのリファレンスは以下

- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/create-bucket.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-public-access-block.html

・参考：[【AWS CLI】ap-northeast-1でcreate-bucketした際にIllegalLocationConstraintExceptionがスローされた場合の対処法](https://qiita.com/eyuta/items/3f39536aae51cdf0d197)

#### `create-bucket`を行う際には`--create-bucket-configuration`を指定しないとエラーになるので注意
```bash
xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api create-bucket --bucket repli-source-cli

An error occurred (IllegalLocationConstraintException) when calling the CreateBucket operation: The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.
```
```
xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api create-bucket --bucket repli-source-cli --region ap-northeast-1

An error occurred (IllegalLocationConstraintException) when calling the CreateBucket operation: The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.
```

### S3（レプリケーション元）の設定
レプリケーションの設定を行うconfigurationのJSONが今回はかなり大きめなのでテンプレ（スケルトン）を作成して、それを編集するようにする
また、IAMロールではなく、サービスロールをレプリケーション設定に渡す（`"Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/replication-service-role"`）

```bash
aws s3api put-bucket-replication --generate-cli-skeleton > replication-config.json
aws s3api put-bucket-versioning --bucket repli-source-cli --versioning-configuration Status=Enabled
aws s3api put-bucket-replication --bucket repli-source-cli --replication-configuration file://replication-config.json
```
```json:replication-config.json
{
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/replication-service-role",
    "Rules": [
        {
            "ID": "sync_to_destinat_cli",
            "Priority": 0,
            "Status": "Enabled",
            "Filter": {
                "Prefix": ""
            },
            "Destination": {
                "Bucket": "arn:aws:s3:::repli-destinat-cli",
                "ReplicationTime": {
                    "Status": "Enabled",
                    "Time": {
                        "Minutes": 15
                    }
                },
                "Metrics": {
                    "Status": "Enabled",
                    "EventThreshold": {
                        "Minutes": 15
                    }
                }
            },
            "DeleteMarkerReplication": {
                "Status": "Disabled"
            }
        }
    ]
}
```

AWS CLIコマンドのリファレンスは以下

- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-replication.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-versioning.html

・参考：[JSON または YAML 入力ファイルからの AWS CLI スケルトンと入力パラメータの生成](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-skeleton.html)

#### `put-bucket-replication`を行う際にはバケットのバージョン管理が有効になっている必要があるので注意
```bash
xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api put-bucket-replication --bucket repli-source-cli --replication-configuration file://replication-config.json

An error occurred (InvalidRequest) when calling the PutBucketReplication operation: Versioning must be 'Enabled' on the bucket to apply a replication configuration
```

#### `--replication-configuration`のJSONについて、`Rule`内に`"Filter": {"Prefix": ""}`がないとエラー[^1]になるので注意
[^1]: `An error occurred (MalformedXML) when calling the PutBucketReplication operation: The XML you provided was not well-formed or did not validate against our published schema`<br>参考：[Python Boto3 PutBucketReplication operation: The XML you provided was not well-formed or did not validate against our published schema](https://stackoverflow.com/questions/59885965/python-boto3-putbucketreplication-operation-the-xml-you-provided-was-not-well-f)

```
"Filter": {
   "Prefix": ""
}
```

### S3（レプリケーション先）の設定
バケットのバージョン管理が有効になっている必要があるのでそれを設定する

```bash
aws s3api put-bucket-versioning --bucket repli-destinat-cli --versioning-configuration Status=Enabled
```

### 実際にレプリケーションを確認してみる
レプリケーション元に`レプリケーションのテスト用.txt`を追加して、レプリケーション先のオブジェクトを取得してみると・・・

```bash
aws s3 cp レプリケーションのテスト用.txt s3://repli-source-cli

xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api head-object \--bucket repli-source-cli --key レプリケーションのテスト用.txt
{
    ...,
    "ReplicationStatus": "COMPLETED"
}

xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api list-objects --bucket repli-destinat-cli
{
    "Contents": [
        {
            "Key": "レプリケーションのテスト用.txt",
            "LastModified": "2021-09-06T14:40:46+00:00",
            ...,
            "Owner": {...}
        }
    ]
}

xxxxxxxxxx:/mnt/c/Users/user/OneDrive/ドキュメント$ aws s3api head-object --bucket repli-destinat-cli --key レプリケーションのテスト用.txt
{
    ...,
    "ReplicationStatus": "REPLICA"
}
```

`"ReplicationStatus": "COMPLETED"`・`"ReplicationStatus": "REPLICA"`となっているようにレプリケーションが成功している事が分かる

AWS CLIコマンドのリファレンスは以下

- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/cp.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-object.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/list-objects.html

## まとめ
レプリケーションはかなり簡単に設定できる
また、IAMロールでもサービスロールでもレプリケーションは設定できる

## おまけ
### 作成したサービスロールは、ちゃんとサービスロールになっているか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/c6c8569d-f6a2-1b69-b1f2-8c8ac6e694c6.png)
※`パス`の部分が`/service-role/`になっているように、これはIAMロールではなくサービスロール