Terraform で DynamoDB の Table を作って IAM Policy でアクセス制限

やりたいこと

* Terraform で DynamoDB の Table を作る。
* 既存のテーブルに読み込みだけが出来る IAM Role を割り当てる。
* 権限が設定されたかテスト。

## Terraform で DynamoDB の Table を作る。

まず AWS ルートユーザや IAM ユーザで aws コマンドが動く事を確認します。

```shell
$ export AWS_PROFILE=hoge
$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                     hoge           manual    --profile
access_key                  hogehoge shared-credentials-file
secret_key     ********************* shared-credentials-file
    region                 us-west-2      config-file    ~/.aws/config
... 適切な access_key, secret_key, region が表示されると OK。
```

次のような Terraform file で [aws_dynamodb_table](https://www.terraform.io/docs/providers/aws/r/dynamodb_table.html) を作ります。

```tf:dynamodb.tf
resource "aws_dynamodb_table" "music" {
  name           = "Music"
  read_capacity  = 1
  write_capacity = 1
  hash_key       = "Artist"
  range_key      = "SongTitle"

  attribute {
    name = "Artist"
    type = "S"
  }

  attribute {
    name = "SongTitle"
    type = "S"
  }
}
```

初期化

    terraform init

設定

    terraform apply

これで、[DynamoDB へのアクセス » CLI の使用](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Tools.CLI.html) と同じテーブルが出来ます。

動作確認

```shell
$ aws dynamodb put-item --table-name Music --item '{"Artist": {"S": "No One You Know"}, "SongTitle": {"S": "Call Me Today"}, "AlbumTitle": {"S": "Somewhat Famous"}}'
$ aws dynamodb query --table-name Music --key-condition-expression 'Artist = :a' --expression-attribute-values '{ ":a": {"S": "No One You Know"}}'
{
    "Count": 1,
    "Items": [
        {
            "AlbumTitle": {
                "S": "Somewhat Famous"
            },
            "SongTitle": {
                "S": "Call Me Today"
            },
            "Artist": {
                "S": "No One You Know"
            }
        }
    ],
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```

削除

    terraform destroy

terraform だと削除が簡単なのが良いですね。

## 既存のテーブルに Query だけが出来る IAM Policy を作る。

次に、うっかりテーブルを変更してしまわないように、作ったテーブルの Query と GetItem だけが出来る IAM Policy を作ります。

AWS では、テーブル等すべてのリソースに ARN が付きます。DynamoDB のテーブルは以下の ARN で表します。

    arn:aws:dynamodb:region:account-id:table/table-name

例えばもしも account-id が 123456789012 で region が us-west-2 なら、上の例は `arn:aws:dynamodb:us-west-2:123456789012:table/Music` です。terraform で作った ARN は `terraform show` で確認出来ます。

このテーブルのアクセス権を設定するには [Policy 言語](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies.html) を使います。DynamoDB の例が [Amazon DynamoDB でアイデンティティベースのポリシー (IAM ポリシー) を使用する](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/using-identity-based-policies.html) に色々あります。

Policy を Terraform で定義する場合は [aws_iam_policy](https://www.terraform.io/docs/providers/aws/r/iam_policy.html) に埋め込む事が出来ます。Music の読み書きを行う Policy は以下のようになります。

```tf
resource "aws_iam_policy" "music" {
  name        = "music_policy"
  description = "DynamoDB Music policy"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "dynamodb:Query",
        "dynamodb:GetItem"
      ],
      "Effect": "Allow",
      "Resource": "${aws_dynamodb_table.music.arn}"
    }
  ]
}
EOF
}
```

## IAM Role を作る。

作った Policy で制限がかかっているか試すにはいくつか方法があります。ここでは IAM Role を作って自分の IAM User から切り替えて使ってみます。IAM Role を作るには [aws_iam_role](https://www.terraform.io/docs/providers/aws/r/iam_role.html) を使います。assume_role_policy で Role を与える対象 (Principal) を選びます。実用上では Principal に `ec2.amazonaws.com` 等を選んで特定の AWS サービスに IAM Role を与える事が多いかと思いますが、ここではテストが簡単なように Principal として今まで作業してきた自分の IAM User を設定し、aws cli で Role を切り替えて使うことします。

```tf
data "aws_caller_identity" "current" {}

resource "aws_iam_role" "music" {
  name = "music_role"

  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "sts:AssumeRole",
        "Effect": "Allow",
        "Principal": {
          "AWS": "${data.aws_caller_identity.current.arn}"
        }
      }
    ]
  }
  EOF
}
```

このように `aws_iam_role` の `assume_role_policy` も `aws_iam_policy` と同様 Policy 言語で記述します。IAM User を Principal として指定するためには ARN を設定しますが、次のような種類があります。

* アカウント 123456789012 の任意のユーザ: arn:aws:iam::123456789012:root
* アカウント 123456789012 のユーザ hoge: arn:aws:iam::123456789012:user/hoge

上の例では Terraform の [aws_caller_identity](https://www.terraform.io/docs/providers/aws/d/caller_identity.html) 機能を使って現在のユーザの ARN を取得しているため、実際の ARN をベタ書きする必要がありません。便利。

## IAM Policy と IAM Role を関連付ける

これも Terraform だと参照を使えるので非常に簡単です。

```tf
resource "aws_iam_role_policy_attachment" "music" {
  role       = "${aws_iam_role.music.name}"
  policy_arn = "${aws_iam_policy.music.arn}"
}
```

ここまでで Terraform の設定は終わりなので、`terraform apply` で再度反映させます。

## テスト

[IAM ロールへの切り替え](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-cli.html) を参考に作った IAM Role に切り替えてテストしてみます。

もしも現在 AWS_PROFILE=hoge を使っていて、デフォルト region が `us-west-2` で、アカウント ID が 123456789012 の場合、`~/.aws/config` に以下を追記します。
role_arn は `terraform show` で aws_iam_role.music: の欄に出てくる arn です。

```ini:~/.aws/config
...
[profile music]
role_arn = arn:aws:iam::123456789012:role/music_role
source_profile = hoge
region = us-west-2
```

このように設定すると、music という aws profile で music_role が有効になります。例えば `--profile music` をつけて DynamoDB にアクセスしてみます。

```shell
$ aws dynamodb query --profile music --table-name Music --key-condition-expression 'Artist = :a' --expression-attribute-values '{ ":a": {"S": "No One You Know"}}'
{
    "Count": 1, 
    "Items": [
        {
            "AlbumTitle": {
                "S": "Somewhat Famous"
            }, 
            "SongTitle": {
                "S": "Call Me Today"
            }, 
            "Artist": {
                "S": "No One You Know"
            }
        }
    ], 
    "ScannedCount": 1, 
    "ConsumedCapacity": null
}
```

のように query は成功しますが、

```shell
$ aws dynamodb put-item --profile music --table-name Music --item '{"Artist": {"S": "No One You Know"}, "SongTitle": {"S": "Call Me Today"}, "AlbumTitle": {"S": "Somewhat Famous"}}'

An error occurred (AccessDeniedException) when calling the PutItem operation: User: arn:aws:sts::123456789012:assumed-role/music_role/botocore-session-1557893071 is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-west-2:123456789012:table/Music
```

のように put-item は目出度くエラーになります。

実際に業務で使う時はこの IAM Role を EC2 とか Kubernetes pod とかに割り当てると、ACCESS_KEY を書かなくても適切な権限を与えられるので便利という物です。