AWS Key Management System (KMS) を外部アカウントから利用するための設定

# まとめ

- AWS KMSのアクセス権限ポリシーは、Customer Master Key(CMK)のキーポリシーと利用者(IAMユーザ/ロール)のIAMポリシーの2箇所に記述できる。評価論理は[IAMポリシーのそれ](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_evaluation-logic.html)と同様である。
- 外部アカウントへのCMKの利用許可設定は、クロスアカウントでのリソースへのアクセスとなるため、キーポリシーと外部アカウントの利用者の2箇所に許可設定を記述する必要がある。

# 実験

実験として、2つのアカウント間でKMS CMKの利用ポリシーを設定し、S3デフォルト暗号化(SSE-KMS)されたバケットにオブジェクトをGet, Putを試みた。
(AWS CLIでも確認できるが、コマンドが複雑なため結果がわかりやすいS3バケットで確かめることにする)

## 実験概要

2つのAWSアカウント KeyStoreAccount, KeyUserAccount を用意する。
KeyStoreAccount では以下を設定する。

- AWS KMS CMKの作成、キーポリシーの設定
- S3 バケットの作成、バケットポリシーの設定

KeyUserAccount では以下を設定する。

- IAM Userの作成、AWS KMS CMKキー利用ポリシーの設定

KeyUserAccount の IAM User 権限を用いてS3バケットにアクセスし、オブジェクトのGet, Putができることを確かめる。
加えて、権限設定を変えた状態でS3バケットへのアクセスを行い、2アカウント間でKMS鍵を利用するために必要な設定を確かめる。

## 準備

- AWSアカウント KeyStoreAccount, KeyUserAccount を用意する。
- AWS CLI を設定する

## IAM User を作成する

AWSアカウント KeyUserAccount 上で IAMユーザ `kms-test-user` を作成し、アクセスキーとシークレットキーを控える。

## AWS KMS CMK の作成

KeyStoreAccount 上で AWS KMS CMK を作成し、ARNを控える
マネジメントコンソールでアクセスすると、キーポリシーがGUIで表示される。しかし、実際はJSONで記述されているため、GUIでは選択できないような設定も可能である。コンソールのJSONタブを選択すると、JSONファイルを直接編集できる。 SID `Allow use of the key` のブロックを以下のように変更する。(↓はIAMユーザ admin を作成して KeyStoreAccount にログインしていた場合の設定である。)

```json
    {
      "Sid": "Allow use of the key",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::{{ KeyStoreAccountのアカウント番号 }}:user/admin",
          "arn:aws:iam::{{ KeyUserAccountのアカウント番号 }}:user:kms-test-user"
        ]
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    },
```

## S3バケットの作成

KeyStoreAccount 上でS3バケットを作成する。バケットのオプションでデフォルト暗号化(SSE-KMS)を設定し、以下をバケットポリシーに追記する。

```json
{
   "Version":"2012-10-17",
   "Id":"PutObjPolicy",
   "Statement":[
      {
         "Sid":"DenyUnEncryptedObjectUploads",
         "Effect":"Deny",
         "Principal":"*",
         "Action":"s3:PutObject",
         "Resource":"arn:aws:s3:::{{ 作成したバケット名 }}/*",
         "Condition":{
            "StringNotEquals":{
               "s3:x-amz-server-side-encryption":"aws:kms"
            }
         }
      },
      {
         "Sid": "Example permissions",
         "Effect": "Allow",
         "Principal": {
             "AWS": "arn:aws:iam::{{ KeyUserAccountのアカウント番号 }}:root"
         },
         "Action": [
             "s3:*"
         ],
         "Resource": [
             "arn:aws:s3:::{{ 作成したバケット名 }}/*",
             "arn:aws:s3:::{{ 作成したバケット名 }}"
         ]
      }
   ]
}
```

## IAM User のポリシー設定

IAMユーザ `kms-test-user` に以下ポリシーの権限を付与する。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "{{ 作成したAWS KMS CMKのARN }}"
    },
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::{{ 作成したバケット名 }}/*"
    }
  ]
}
```

## S3バケットにオブジェクトを配置する

適当なファイルを作成し、 `kms-test-user` のクレデンシャル情報を用いてS3バケットにオブジェクトを設定する

### クレデンシャル情報の設定

控えておいたアクセスキー/シークレットキー情報を環境変数に設定する。環境変数の管理は[Direnv](https://direnv.net/)がおすすめ。

```
export AWS_ACCESS_KEY_ID={{ アクセスキー }}
export AWS_SECRET_ACCESS_KEY={{ シークレットキー }}
```

### オブジェクトの配置

以下のコマンドでS3バケットにオブジェクトを配置する。PutObjectの際には暗号化設定を入れる必要があるので、 `--sse` および `--sse-kms-key-id` を指定している。

```bash
$ aws s3 cp ./test.txt s3://{{ 作成したバケット名 }} --sse "aws:kms" --sse-kms-key-id {{ AWS KMS CMKのARN }}
upload: ./test.txt to s3://{[ 作成したバケット名 }}/test.txt
```

無事にオブジェクトが配置できた。

#### 補足1
オブジェクトの所有者が KeyUserAccount となるため、 KeyStoreAccount からは test.txt へのGetアクションが拒否される。

#### 補足2
`--sse-kms-key-id` にはCMKのIDも指定できるが、内部でARNを生成する際にクレデンシャルが所属するアカウント番号が補完されるためキー名がない、とエラーになる。

```
$ aws s3 cp ./test.txt s3://{{ 作成したバケット名 }} --sse "aws:kms" --sse-kms-key-id {{ AWS KMS CMKのID }}
upload failed: ./test.txt to s3://{{ 作成したバケット名 }}/test.txt An error occurred (KMS.NotFoundException) when calling the PutObject operation: Key 'arn:aws:kms:{{ Region }}:{{ KeyUserAccountのアカウント番号 }}:key/{{ AWS KMS CMKのID}}' does not exist
```

## 追加実験 権限を変えて試してみる

### KeyUserAccount 側の記述を削除する
上記手順では、CMKのキーポリシーに `kms-test-user` への利用権限を記述している。IAMポリシーの評価論理に照らし合わせれば、 `kms-test-user` 側のポリシーに何も書かなくてもCMKは利用可能となるはずである。

そこで、 `kms-test-user` のポリシーから CMK の権限に関する記述を削除してみる。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::{{ 作成したバケット名 }}/*"
    }
  ]
}
```

この状態で `s3 cp` を実行すると、下記のように失敗する。

```shell
$ aws s3 cp ./test.txt s3://{{ 作成したバケット名 }} --sse "aws:kms" --sse-kms-key-id {{ AWS KMS CMKのARN }}
upload failed: ./test.txt to s3://{{ 作成したバケット名 }}/test.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
```

おそらく、クロスアカウントでのリソース利用に関わる権限設定に引っかかってるものと思われる。

### AWS KMS CMK キーポリシーの記述を削除する
同様に、CMKのキーポリシーから `kms-test-user` の権限を削除してもファイルのアップロードに失敗した。


# 参考

- [AWS KMS リソースへのアクセス管理の概要](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/control-access-overview.html)
- [AWS KMS で管理されたキーによるサーバー側の暗号化 (SSE-KMS) を使用したデータの保護](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingKMSEncryption.html)
- [AWS Command Line Interface ユーザーガイド | Environment Variables](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-environment.html)
- [Amazon Simple Storage Service 開発者ガイド | 例 2: バケット所有者がクロスアカウントのバケットのアクセス許可を付与](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-walkthroughs-managing-access-example2.html)
- [AWS CLI Command Reference | aws . s3 . cp](https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html)