【AWS】AWSアカウントをまたいでS3 Bucket間コピーする（解説編）【クロスアカウント】

# はじめに
2つのAWSアカウントにそれぞれ存在するS3 Bucket間で、ファイルを転送するケースについて、S3 BucketのBucket Policyを付与せずに実現する方法は無いか調べました。
アクセス権限管理の都合上、出来る限りBucket Policyを付与せず、IAM Policyだけで全て管理したいものです。
また会社間でファイル転送をするうえで、S3 Bucketの管理ポリシー上、Bucket Policyを容易に付与できない場合もあります。

なんとかIAM Policyだけで完結できないか調べてみました。
結果、出来ないという結論に至りました。

本稿では、何故それが出来ないのか、その仕組みから説明します。

# TL;DR
- 権限管理について
    - AWSアカウントをまたいだS3 Bucket間コピーは、どちらかのS3 BucketにBucket Policyが必要になる
    - Least Privilegeの原則に従い、Bucket Policyでの許可は最小限に
- 仕組みについて
    - S3 Bucket間コピーは、1つのAWS APIリクエストで行われる
    - AWS S3 APIの実行には、必ずAWS Credentials（AccessKeyId/SecretAccessKey）による署名が必要
    - 1つのAWS APIには、複数のAWS Credentialsで署名することはできない


# やりたかったこと
- 2つのAWSアカウントにS3 Bucketが1つずつあり、両S3 Bucket間でファイルをコピーする
- ファイルコピー時にはローカルにダウンロードしない
- S3 BucketにはBucket Policyを付与したくない
- IAM User/Group/Roleに付与するPermissionだけで権限を管理したい


# S3 Bucket間転送とは
下記のKnowlege Centerの記事にもありますが、あるS3 Bucket内のファイルを、別のS3 Bucketにコピー／移動させたい、というケースはよくあると思います。
シンプルに、いったんローカルにダウンロードしてから、目的のS3 Bucketにアップロードするということもできますが、無駄なトラフィックが発生し、また時間もかかります。
そのため、S3にはS3 Bucket間でファイルのコピーを行う方法が準備されています。

Amazon S3 バケットの間でオブジェクトを移動するにはどうしたらよいですか?
https://aws.amazon.com/jp/premiumsupport/knowledge-center/move-objects-s3-bucket/

S3 Bucket間コピーが、同一AWSアカウント内のBucketであれば話は簡単です。
下図のように、コピー操作を行う主体（Entity）に、当該S3 Bucketの読み取り／書き込み権限を付与するだけです。
![single_AWS_account.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/e494784f-9148-1cbb-7b73-fb39bd35c8e1.png)


下記ドキュメントにあるように、双方のS3 Bucketがそれぞれ別のAWSアカウントに存在する場合には、話が違ってきます。

Amazon S3 リソースの移行
https://aws.amazon.com/jp/premiumsupport/knowledge-center/account-transfer-s3/

以下より、このパターンについて詳しく説明します。


# 前提となる仕組み・要素技術
本題に入る前に、理解しておくべき要素技術を説明します。

## AWS S3 API
AWS S3 APIをひもとくと、PUT Object操作には下記の2種類あることが分かります。

- [PUT Object](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html)
- [PUT Object - Copy](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html)

前者はPut Object、つまりローカルからのアップロードに用いられるものとなります。
後者はコピー用のAPIとなり、転送元・転送先に、S3 Objectを指定できるAPIとなります。

後者のAPIドキュメントを読んでみますと、下記のように書いてあります。

PUT Object - Copy
https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html

> All copy requests must be authenticated and cannot contain a message body. Additionally, you must have READ access to the source object and WRITE access to the destination bucket. For more information, see REST Authentication.

コピーするためのリクエストは全て認証が必要であること、コピー元S3オブジェクトへの読み取りアクセスと、コピー先Bucketへの書き込みアクセス権限が必要であることが読み取れます。
また、コピーのリクエストは1回のAPIで行われる必要があるでしょう。

文末にある「REST Authentication」とは、AWS S3 APIを実行する際には、AWS Credentialsによる署名が必要である、ということです。
下記公式ドキュメントに詳しい記載があります。

Signing and Authenticating REST Requests
https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html

## AWS Credentials
これは、なじみ深いところで言えば、IAM Userが発行できるAccessKeyIdおよびSecretAccessKeyの組み合わせです。AWS CLIで設定するあれです。
1つのIAM Userにひもづき、当該ユーザの権限でAWS APIを実行することができます。

AWS Credentialsには、上記のように長期にわたって有効なものと、一時的に発行されるものがあります。
一時的なものはTemporary Credentialsと呼ばれています。
なお、Temporary Credentials発行時、AccessKeyId/SecretAccessKeyに加え、SessionTokenというものも併せて発行されます。

具体的な内容は下記ドキュメントを参照ください。
なお、下記ドキュメントはAccessKeyIdおよびSecretAccessKeyのペアのことを「Access Keys」と表記しておりますが、一般名称ぽくて紛らわしいこと、他のドキュメントでは「AWS Credentials」と表記していることが多いことから、本稿でも「AWS Credentials」と書くことにします。
\# 公式ドキュメントでも、結構表記がばらついているようです。
\# AWS SDK for Javaでは、AWSCredentialsというようなクラス名が使われているため、ドキュメント上もこの表記になっているようです。

Understanding and Getting Your Security Credentials - Amazon Web Services -> Access Keys (Access Key ID and Secret Access Key)
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys


Temporary Security Credentialsは、一時的なAWS Credentialsとなります。
Temporary Credentialsとも表記されます。

Temporary Security Credentials
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html


Temporary Credentialsの利用でいちばん身近なものは、IAM Roles for Amazon EC2で利用される、EC2 Instance Profileかと思います。
詳しい説明は割愛しますが、EC2内で事項されるプログラムから、EC2に付与されたRoleにひもづくTemporary Credentialsを取得することができます。
このTemporary Credentialsを利用して、AWS APIを実行することができます。

IAM Roles for Amazon EC2 - Amazon Elastic Compute Cloud
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html


## AssumeRole
Temporary Security Credentialsの発行と密接な関係がある仕組みです。

多くの場合、AWS APIを実行する主体はIAM Userになるかと思います。AWS Management ConsoleやAWS CLIの操作は、ログインしたIAM User／設定したAWS Credentialsに対応するIAM Userの権限で行われます。

しかしながら、IAM Userにひもづけることができない主体が、AWS APIの実行を必要とする場合があります。
このような場合にIAM Roleが用いられます。

シンプルにまとめると次のように言うことができます。

**ある主体が、その権限を得たいIAM Roleを指定してAssumeRole APIを発行することにより、Temporary Security Credentialsを取得する。**

IAM ロール
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html

設定上のポイントは以下の2つだけです。
1. AssumeRoleしたいIAM User/GroupのPermissionsに、AssumeRoleを実行する許可を指定する。
2. IAM RoleのTrustRelationshipにて、AssumeRoleの実行を許可させる主体を指定する。

具体的な設定を見ていきましょう。以下の構成を仮定します。

- AssumeRole元IAM User
    - AWSアカウントID: 111111111111111
    - IAM User: src-user
- AssumeRoleされる側のIAM Role
    - AWSアカウントID: 999999999999999
    - IAM Role: dst-role


- AssumeRoleしたいUser/GroupのPermissions

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::999999999999999:role/dst-role"
            ]
        }
    ]
}
```


- AssumeRoleされる側のIAM RoleのTrustRelationships

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111111:user/src-user"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

AssumeRoleしたいIAM Userであるsrc-userの権限で、下記のようにAssumeRoleを実行します。
「AccessKeyId」「SecretAccessKey」「SesionToken」という値が返却されたことが分かります。
この3つの情報を使ってAWS APIを発行することができます。この時の権限は、AssumeRoleで指定したRoleのものとなります。


```bash
$ aws sts assume-role --role-arn "arn:aws:iam::999999999999999:role/dst-role" --role-session-name "you-can-put-any-string"
{
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAXXXXXXXXXXXXXXXXX:you-can-put-any-string",
        "Arn": "arn:aws:sts::999999999999999:assumed-role/dst-role/you-can-put-any-string"
    },
    "Credentials": {
        "SecretAccessKey": "0hq4**********************************",
        "SessionToken": "FQoD************************************************************************************************************************************************************************************************************",
        "Expiration": "2018-04-29T12:57:34Z",
        "AccessKeyId": "ASIAXXXXXXXXXXXXXXXX"
    }
}
```

ちなみにオプション「--role-session-name」の値は必須ですが、任意の文字列を指定することができます。
当該AWS APIの詳しい仕様は下記公式ドキュメントを参照ください。

AssumeRole - AWS Security Token Service
https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html


## AWS APIと署名（Sign V4）
AWSリソースに対する操作は、より低レイヤをたどっていくと、最終的にREST APIに行き着きます。
AWSとしては、発行されたAPIが正統なIAM Entity（User/Role）によって発行されたものなのか、またどのIAM Entityによって発行されたものか、チェックする必要があります。
これを担保するのが、AWS APIに対する署名です。現在は「AWS Sigunature Version 4」という方式が利用されています。

Authenticating Requests (AWS Signature Version 4)
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

詳細は割愛しますが、概ね以下のような仕組みとなっています。

1. AWS APIを発行する際に、そのリクエスト内容を元に署名を作る。SecretAccessKeyを鍵として用いて署名を作成する。
2. AWS APIに当該署名を付与してリクエストする。
3. 当該リクエストを受け取ったAWSは、1と同一の手順で署名を生成し、リクエストに付与されたものと照合する。

上記の仕組みから、1つのAWS APIリクエストに対し、複数のAWS Credentialsで署名することは出来ないことが分かります。

## Resource-based policies
通常、AWSリソースに対するアクセス可否を制御するためには、IAM User/Group/Roleに必要なPolicyを付与していきます。
S3などの幾つかのAWSリソースは、Resource-based policiesと呼ばれるPolicyを持つことができ、アクセス可否を制御することができます。

How IAM Roles Differ from Resource-based Policies - AWS Identity and Access Management
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_compare-resource-policies.html


# 本題
上記までの要素技術を踏まえて、初めて本題に入ることができます。

## 想定構成
以下の想定環境をもとに説明します。

- ファイルコピー元AWSアカウント
    - AWSアカウントID: 111111111111111
    - S3 Bucket: inter-bucket-copy-src
    - IAM User: src-user
    - IAM Role: src-role
- ファイルコピー先AWSアカウント
    - AWSアカウントID: 999999999999999
    - S3 Bucket: inter-bucket-copy-dst
    - IAM User: dst-user
    - IAM Role: dst-role

## 理想的な構成
アクセス権限の管理は極力シンプルにしたいです。
そのために、理想的な構成を考えてみます。

- S3 Bucket Policyは付与しない
- 必要なPolicy/Permissionsは、それぞれのAWSアカウント内で完結させたい
    - inter-bucket-copy-srcに対するPermissionsは、AWSアカウント111111111111111内のIAM Entityのみに付与する
    - inter-bucket-copy-dstに対するPermissionsは、AWSアカウント999999999999999内のIAM Entityのみに付与する
- AssumeRoleを活用して、権限を切り替える（＝必要なtemporary credentialsを適宜発行する）


上記要件を実現するための構成、手順は下記の通りです。
ですがこれは実現できません。
![ideal_structure.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/7554be23-0ed6-da40-f8a0-eb8ce41b44b9.png)

上記内容を詳しく見ていきましょう。
まず、AWSリソースの構成は下記のようになります。

- AWSアカウント: 111111111111111
    - S3 Bucket: inter-bucket-copy-src
        - Bucket Policyなし
    - IAM User: src-userのPermissions
        - inter-bucket-copy-srcへのListBucket
        - inter-bucket-copy-srcへのGetObject
        - dst-roleへのAssumeRole
- AWSアカウント: 999999999999999
    - S3 Bucket: inter-bucket-copy-dst
        - Bucket Policyなし
    - IAM Role: dst-roleのPermissions
        - inter-bucket-copy-dstへのPutObject
    - IAM Role: dst-roleのTrustRelationships
        - src-userからのAssumeRole

下記の手順でS3 Bucket間コピーが実現できれば理想です。

1. src-userは、dst-roleをAssumeRoleするよう、AWS APIを発行する。
2. temporary credentialsを取得する。temporary credentialsはdst-roleの権限でAWS APIを発行できる。
3. src-userが持つ権限とtemporary credentialsが持つ権限を組み合わせて、PUT Object - Copyを発行する。**※不可能**
4. inter-bucket-copy-src上のファイルがinter-bucket-copy-dstにコピーされる。

複数のAWS Credentialsを組み合わせてAWS APIを発行することは出来ないため、上記の実現は不可能です。

先に見てきたとおり、PUT Object - Copyは1回のAWS APIで実行される必要があります。
Sign V4の仕組みを見ても、1つのAWS APIリクエストに対して複数のAWS Credentialsで署名する方法は無さそうです。


## 実現できた構成
どうやら、S3 BucketにBucket Policyを付与せずに実現する方法は無いようです。
どちらか一方のS3 BucketにBucket Policyを付与して実現する方法を考えてみます。

### Type1: コピー先S3 BucketにBucket Policyを付与する
こちらはシンプルです。コピー先となるinter-bucket-copy-dstにBucket Policyを付与する方法です。
具体的には以下の2つの設定が必要です。

- src-userのPermissionsに、inter-bucket-copy-dstへのPutObject権限を付与する
- inter-bucket-copy-dstに、src-userからのPutObject権限を付与する

S3はResource-basedなPolicyであるBucket Policyを持っていますので、S3に対する操作はIAM EntityのPolicyによる権限のチェックと、Bucket Policyによる権限のチェックが行われます。
そのため、対象S3 BucketへのPutObject権限が両者に必要となります。

図にまとめると下記のようになります。
![type1.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/73fed8b3-ae3a-a7d7-0eb3-5ae773234b3b.png)

### Type2: コピー元S3 BucketにBucket Policyを付与する（より複雑）
コピー元となるinter-bucket-copy-srcに、Bucket Policyを付与する方法です。
図にまとめると下記のようになりますが、少し複雑です。
![type2.PNG](https://qiita-image-store.s3.amazonaws.com/0/137777/1161ee6d-553d-780c-e709-0990f8fc8905.png)


以下から詳しく見ていきましょう。
まず必要な構成を整理します。

- AWSアカウント: 111111111111111
    - S3 Bucket: inter-bucket-copy-src
        - dst-roleからのListBucket
        - dst-roleからのGetObject
    - IAM User: src-userのPermissions
        - ~~inter-bucket-copy-srcへのListBucket~~
        - ~~inter-bucket-copy-srcへのGetObject~~
        - dst-roleへのAssumeRole
- AWSアカウント: 999999999999999
    - S3 Bucket: inter-bucket-copy-dst
        - Bucket Policyなし
    - IAM Role: dst-roleのPermissions
        - inter-bucket-copy-srcへのListBucket
        - inter-bucket-copy-srcへのGetObject
        - inter-bucket-copy-dstへのPutObject
    - IAM Role: dst-roleのTrustRelationships
        - src-userからのAssumeRole

下記の手順でS3 Bucket間コピーが実現できるようになります。

1. src-userは、dst-roleをAssumeRoleするよう、AWS APIを発行する。
2. temporary credentialsを取得する。temporary credentialsはdst-roleの権限でAWS APIを発行できる。
3. temporary credentialsを利用してPUT Object - Copyを発行する。
4. inter-bucket-copy-src上のファイルがinter-bucket-copy-dstにコピーされる。

ここで、src-userのpermissionsに注目します。src-userに必要な権限はdst-roleをAssumeRoleする権限のみとなり、S3 Bucketの操作に関する権限は不要になります（というより使われなくなる）。

逆に、dst-roleには、inter-bucket-copy-srcの参照権限が必要となります。

AssumeRoleの要否の差はありますが、先ほどのType1のパターンと鏡写しのような構成になっていることが分かります。

## 補足
これまで、操作の起点はIAM Userと仮定していましたが、もちろんEC2 Instance Profileを利用することもできます。
EC2 Instance内で取得したTemporary Credentialsを使い、dst-roleをAssumeRoleすることも可能です。

Type2のケースについて、具体的な設定内容・手順等を下記記事にまとめていますので、併せて参照ください。

【AWS】AWSアカウントをまたいでS3 Bucket間コピーする（実践編）【クロスアカウント】
https://qiita.com/tmiki/items/db73035624b6f7d1225e


# おわりに
AWSアカウントとまたぐS3 Bucket間コピーは、会社間のファイル授受であるケースもあるかと思います。
どちらか一方のセキュリティポリシー／力関係により、既存のS3 BucketへのBucket Policyの付与が許されない場合もあるでしょう。
このような場合には、要件・制約をきちんと理解して、実現可能な構成を状況によって使い分ける必要があります。