AWS CLI を利用してIAMロールを引き受ける

## 概要

### 背景

よく、「◯◯の操作をする前に、`export AWS_PROFILE=some-developer`する」みたいな操作をAWSに関するコマンドラインからの作業の際にするのですが、これが何をやっているのか手を動かしながら理解を深めていきました。

ここでは、AWS IAM のロール、AWS STS に関しての概要と、あるプリンシパルエンティティ（ユーザー、グループ、ロール）から別のロールに切り替えるにはどうしたらいいのかを確認します。

### やったこと

あらかじめs3-developerロールに、信頼されたプリンシパルエンティティとして、user1を登録しておきます。また、s3-developerロールにS3へアクセスできるポリシーを付与しておきます。

その上で、以下の図のように AWS STS を利用して、そのロールの一時的な認証情報を取得して、user1が、s3-developerとして（AssumeRoleして）でS3へアクセスできるようにしています。

<img width="512" alt="assume-role.png" src="https://qiita-image-store.s3.amazonaws.com/0/68018/6483bcd4-b08e-5071-b80d-30e4f81376f9.png">


## 前提の確認

### 基本的な用語

まず、用語の意味を確認します。細かいことはここでは説明する必要がないのでざっくりとした説明に留めておきます。

**（IAM）ポリシー**
どのサービスに対して、どのような操作を許可、拒否できるかというルール。要するにAWSサービスへの権限のこと。

**（IAM）ユーザー**
AWSを利用するための認証情報のことで、AWSのアカウント作成者（ルートユーザー）ではなく、AWSのサービスにアクセスする人。コマンドラインツールからアクセスするには、アクセスキーとシークレットアクセスキーのペアが必要。

**（IAM）グループ**
ユーザーの集まりで、グループ単位にポリシーを付与することもできる。認証自体はユーザーが行う。

### ロールの構成要素と役割

**（IAM）ロール**

ユーザーやAWSリソース（EC2とかLambdaとか）が一時的に利用できる権限セットのことです。
これを利用することで、AWSリソースへアクセス権のないユーザー、サービスにアクセス権を委譲することができます。

c.f https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html

> IAM ロールは、特定のアクセス権限を持ち、アカウントで作成できる IAM アイデンティティです。IAM ロールは、 AWSで許可/禁止する操作を決めるアクセス権限ポリシーが関連付けられている AWS アイデンティティであるという点で、IAM ユーザーと似ています。ただし、ユーザーは 1 人の特定の人に一意に関連付けられますが、ロールはそれを必要とする任意の人が引き受けるようになっています。また、ロールには標準の長期認証情報 (パスワードやアクセスキーなど) も関連付けられません。代わりに、ロールを引き受けると、ロールセッション用の一時的なセキュリティ認証情報が提供されます。


要するに、以下の特徴があります。

- 複数のPolicyを持つことができる
- 複数のプリンシパルエンティティが利用できる
- 信頼関係にあるプリンシパルエンティティに一時的な認証情報を提供できる

**ロールの構成要素**

以上のような役割を実現するために、ロールは主に二つの構成要素からなります。

1. ポリシー
2. 信頼ポリシー

最初のポリシーは、ロールに対するAWSリソースへのアクセス権限を表すポリシーです。
二つ目の信頼ポリシーとは、どのプリンシパルエンティティから利用できるのか、つまり権限を委譲できるのかを表すポリシーになります。

例えば以下は、一つのAWSアカウント（ID: 111122223333）のルートユーザーと、もう一つのAWSアカウント（ID: 444455556666）のuser1という名前のユーザーがロールを引き受けることを示しています。

```json
{
  "Principal": {
    "AWS": [
      "arn:aws:iam::111122223333:root",
      "arn:aws:iam::444455556666:user/user1",
    ]
  },
}
```

こうすることで、どのプリンシパルエンティティにロールを引き受けさせるかを制限することができます。

c.f https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_manage_modify.html

**AWS Security Token Service (AWS STS)**

AWSリソースにアクセスするための一時的セキュリティ認証情報を取得することができます。
c.f https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp.html

そして、信頼されたプリンシパルエンティティからロールの権限セットを引き受ける（AssumeRoleする）際は、STSへAPI呼び出しをしています。そうすることで、STSが要求されたロールに要求したプリンシパルエンティティが信頼されているかどうかを確認し、信頼されている場合はセキュリティ認証情報を発行します。

## CLIからの操作

それでは実際にAWS CLIを使って、AssumeRoleを実現します。

### 全体の流れ

- S3の準備
  - バケットの作成
  - 適当なファイルをアップロードする
- ユーザーの作成
  - デフォルトのユーザーにはポリシーは付与されていないので何もできない状態
- ロールを作成
  - 作成したユーザーを Trust Relationship として持つ
  - S3への読み取りが可能なポリシーを持つ


### S3の準備

まず、ルートユーザー相当の権限を持つユーザーとして、S3のバケットと、それへのファイルのアップロードを済まします。

```bash
$ aws s3api create-bucket --bucket assume-role-bucket --region ap-northeast-1 --create-bucket-configuration LocationConstraint=ap-northeast-1
{
    "Location": "http://assume-role-bucket.s3.amazonaws.com/"
}
$ $ cat << EOF > sample.html
<html>
  <head>
    <title>タイトル</title>
  </head>
  <body>
    aws cli の練習
  </body>
</html>
EOF
$ aws s3 cp sample.html s3://assume-role-bucket
upload: ./sample.html to s3://assume-role-bucket/sample.html
```


### ユーザーの作成

user1という名前のユーザーを作成します。

```bash
$ aws iam create-user --user-name user1
{
    "User": {
        "Path": "/",
        "UserName": "user1",
        "UserId": "AAAAAAAAAAAAAA",
        "Arn": "arn:aws:iam::123456789:user/user1",
        "CreateDate": "2019-03-22T10:29:46Z"
    }
}
```

AWSのコマンドラインツールを利用するには、AccessKeyIDとSecretAccessKeyのペアが必要なので、user1のペアを作成します。（もちろん適当な文字列に置き換えています。）

```bash
$ aws iam create-access-key --user-name user1
{
    "AccessKey": {
        "UserName": "user1",
        "AccessKeyId": "ACCESSKEYID",
        "Status": "Active",
        "SecretAccessKey": "SECRETACCESSKEY",
        "CreateDate": "2019-03-22T10:30:15Z"
    }
}
```

後にuser1として、S3にコマンドラインからアクセスするため、ここで返ってきたキーのペアをCLIが読み取るパスのファイルに格納します。

```bash
# .aws/credentials
[user1]
aws_access_key_id = ACCESSKEYID
aws_secret_access_key = SECRETACCESSKEY
```


### ロールの作成

まず、作成したユーザー（user1）がAssumeRoleできるように、信頼ポリシーの定義をしたJSONファイルを作成します。先ほど作成したuser1のARNが指定されていることが確認できるかと思います。

```bash
$ cat << EOF > s3-developer-policy.json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Principal": { "AWS": "arn:aws:iam::123456789:user/user1" },
        "Action": "sts:AssumeRole"
    }
}
EOF
```

次に、先ほど作った信頼ポリシーの定義のファイルを指定して、s3-developerというロールを作成します。

```bash
$ aws iam create-role --role-name s3-developer --assume-role-policy-document file://s3-developer-policy.json
{
    "Role": {
        "Path": "/",
        "RoleName": "s3-developer",
        "RoleId": "ABAAAAAAAAAAAAAAA",
        "Arn": "arn:aws:iam::123456789:role/s3-developer",
        "CreateDate": "2019-03-22T11:23:54Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": {
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789:user/user1"
                },
                "Action": "sts:AssumeRole"
            }
        }
    }
}
```

これで、s3-developerロールにuser1からAssumeRoleすることができるようになりました。
実際にマネジメントコンソールを確認するとIAMのs3-developerロールにuser1が信頼されたエンティティとして登録されていることが確認できます。

<img width="947" alt="role.png" src="https://qiita-image-store.s3.amazonaws.com/0/68018/fb4fa2b6-e0ca-88af-d3ac-39b93a698eec.png">

そして、コマンドラインからAssumeRoleを実現できるように以下のように設定ファイルを編集します。
これは、`source_profile`にuser1を指定することで、s3-developerロールとしてAWSにCLIからアクセスすると、STSがuser1は信頼関係にあるかどうかを自動的に検索し、一時的な認証情報を取得することができるようになるためです。

c.f https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-role.html
> AWS CLI コマンドでのプロファイル marketingadmin の使用を指定すると、CLI はリンクされた user1 プロファイルの認証情報を自動的に検索し、その認証情報を使用して、指定された IAM ロールの一時的な認証情報を要求します。これらの一時的な認証情報は、次に CLI コマンドを実行するために使用されます。

```bash
# .aws/credentials
[user1]
aws_access_key_id = ACCESSKEYID
aws_secret_access_key = SECRETACCESSKEY

[s3-developer]
role_arn = arn:aws:iam::123456789:role/s3-developer
source_profile = user1
```

ただ、これだけでは、s3-developerは何もポリシーを付与されていないので、S3への読み取りポリシーを付与します。

```bash
$ aws iam attach-role-policy --role-name s3-developer --policy-arn "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
$ aws iam list-attached-role-policies --role-name s3-developer
{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonS3ReadOnlyAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
        }
    ]
}
```

### 実際に試してみる

必要な準備が整ったので、実際にS3にアクセスできるか確認してみます。
まず、user1として、S3のバケットの内容を確認しようとしてみます。

環境変数に先ほど接続設定ファイルに指定したプロファイル名を渡して、そのユーザーの認証情報でアクセスします。

```bash
$ export AWS_PROFILE=user1
$ aws sts get-caller-identity
{
    "UserId": "AAAAAAAAAAAAAA",
    "Account": "123456789",
    "Arn": "arn:aws:iam::123456789:user/user1"
}
```

user1だとAccessDeniedになりました。

```bash
$ aws s3 ls s3://assume-role-bucket/sample.html

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
```

次にs3-developerにAssumeRoleします。

```bash
$ export AWS_PROFILE=s3-developer
$ aws sts get-caller-identity
{
    "UserId": "ABAAAAAAAAAAAAAAA:botocore-session-1553254029",
    "Account": "123456789",
    "Arn": "arn:aws:sts::123456789:assumed-role/s3-developer/botocore-session-1553254029"
}
```

確かに最初にアップロードしたファイルを確認することができました！

```bash
$ aws s3 ls s3://assue-role-bucket/sample.html
2019-03-22 20:06:52        107 sample.html
```