AWSのKMSへのアクセスをIAMグループ単位で管理する (KMSキーポリシーでIAMグループが指定できないよ問題)

# TL; DR

* AWSのKMSのキーポリシーで暗号化/復号化の権限付与する対象のPrincipalにIAMユーザとIAMロールは指定できるのに、なぜだかIAMグループは指定できない。
* IAMグループで管理するにはKMSのキーポリシーでIAMポリシーを有効化するように設定し、別途IAMポリシーをIAMグループに付与することでIAMグループ単位での制御が可能となる

キーポリシー

```json
{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy-2",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:root"},
      "Action": "kms:*",
      "Resource": "*"
    }
  ]
}
```

IAMポリシー => これを暗号化/復号化したいIAMグループに付与する

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowUseOfTheKey",
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:111122223333:key/e18455dd-bf55-4b0a-b0ab-XXXXXXX"
        }
    ]
}
```

# はじめに
AWS KMS（Key Management Service） はデータの暗号化に使用する暗号化キーを簡単に作成および管理できるマネージド型サービスです。KMS自体の説明は適当にググって下さい。

KMSのよいところは暗号化キーへ誰がアクセスしてよいかをIAMの権限管理で制御できるようになるところ。

アプリケーションでDB接続パスワードをデプロイ時にどうやって渡すかみたいな問題はよくあるんだけど、環境変数経由で渡すにしても、じゃあその環境変数はサーバ起動後にどうやって自動で仕込もうかみたいな話になって、暗号化してアプリケーションコードにコミットしておこうかってなるんだけど、じゃあ、その暗号化キーはどうやってサーバに渡すんだっけみたいな鶏卵問題が発生する。

ここでKMSを使えば、あらかじめアプリケーションコードに暗号化した状態でDB接続のパスワードなどの秘匿すべきクレデンシャル情報を仕込んでおき、EC2インスタンス起動時にIAMロールで権限をもらって、KMSから暗号化キーを取得して、復号化するといったことができるようになってうれしい。

# KMSのキーポリシー

ところで、AWSのマネージメントコンソールから普通に新しいマスタキーを作ると誰に権限を与えるかを聞かれるけど、このときキーポリシーが作られている。

AWSのドキュメントにサンプルが乗ってるけど、こんなかんじのができてる。
http://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/key-policies.html

```json
{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy-2",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:root"},
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow access for Key Administrators",
      "Effect": "Allow",
      "Principal": {"AWS": [
        "arn:aws:iam::111122223333:user/KMSAdminUser",
        "arn:aws:iam::111122223333:role/KMSAdminRole"
      ]},
      "Action": [
        "kms:Create*",
        "kms:Describe*",
        "kms:Enable*",
        "kms:List*",
        "kms:Put*",
        "kms:Update*",
        "kms:Revoke*",
        "kms:Disable*",
        "kms:Get*",
        "kms:Delete*",
        "kms:ScheduleKeyDeletion",
        "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Allow use of the key",
      "Effect": "Allow",
      "Principal": {"AWS": [
        "arn:aws:iam::111122223333:user/KMSUser",
        "arn:aws:iam::111122223333:role/KMSRole",
        "arn:aws:iam::444455556666:root"
      ]},
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Allow attachment of persistent resources",
      "Effect": "Allow",
      "Principal": {"AWS": [
        "arn:aws:iam::111122223333:user/KMSUser",
        "arn:aws:iam::111122223333:role/KMSRole",
        "arn:aws:iam::444455556666:root"
      ]},
      "Action": [
        "kms:CreateGrant",
        "kms:ListGrants",
        "kms:RevokeGrant"
      ],
      "Resource": "*",
      "Condition": {"Bool": {"kms:GrantIsForAWSResource": "true"}}
    }
  ]
}
```

で、サーバで復号化するのはIAMロールを使って、あらかじめ暗号化しておく方はそのクレデンシャル情報にアクセスしてよい担当者のIAMグループとか作って、権限付与しておけばよいよねー？っていう当然の発想になる。

なんとなくPrincipalのところに `"arn:aws:iam::111122223333:group/KMSGroup",` みたいなのを書いておけば行けそうな予感がするんだけど、これをやってみるとInvalidArnExceptionというエラーが出て怒られる。えー。

さきほどのドキュメントを見るとこんなことが書いてある。

> プリンシパル –（必須）プリンシパルは、ポリシーステートメントのアクセス権限が適用されるアイデンティティです。プリンシパルとして、AWS アカウント（ルート）、IAM ユーザー、IAM ロール、AWS サービスをキーポリシーで指定できます。IAM グループは有効なプリンシパルではありません。

まじか。なぜだかIAMグループは指定できない。グループって内部的にどういう概念になってるんだろうか。キーごとにIAMユーザ列挙するとか保守性が低いからやりたくないよ。


# 解決策
なんかよい方法ないものかと思って、もうちょっとドキュメントを眺めたら、こんな記載が。

http://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/key-policy-modifying.html

> CMK への複数の IAM ユーザーのアクセスの許可
IAM グループは、キーポリシーで有効なプリンシパルではありません。CMK に複数の IAM ユーザーのアクセスを許可するには、次のいずれかを実行します。
各 IAM ユーザーをキーポリシーに追加します。この手法では、承認されたユーザーのリストが変更されるたびに、キーポリシーを更新する必要があります。
キーポリシーには、IAM ポリシーで CMK キーへのアクセスを許可するステートメントが含まれている必要があります。次に、CMK へのアクセスを許可する IAM ポリシーを作成し、承認された IAM ユーザーを含む IAM グループにそのポリシーをアタッチします。このアプローチを使用すると、承認されたユーザーのリストが変更されたときにポリシーを変更する必要はありません。代わりに、該当する IAM グループに対してそれらのユーザーを追加または削除する必要があります。

というわけで、IAMポリシーと組み合わせればできたのでやり方を書いておく

まず、対象となるキーポリシーをさっきの最初のステートメントだけ残して、以下のようにする。（※ `111122223333` のアカウントIDのところは適宜読み替えて下さい。）

```json
{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy-2",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:root"},
      "Action": "kms:*",
      "Resource": "*"
    }
  ]
}
```

キーポリシー側で、このおまじないを書くとなぜかIAM側で権限制御できるようになる。これだけ見るとrootはなんでもできるよってゆってるだけに見えるんだけど。。。この時点でIAMとしてAdministratorsを持っている人は全アクセス可能になる。

で、次に以下のようなIAMポリシーを作って、暗号化/復号化する権限をIAMグループに付与すると、指定のグループで指定のキーにアクセスできるようになる。
※Resourceのところは対象のキーIDのARNで読み替えて下さい。ちなみにキーIDのところはキーエイリアスでの指定は不可でした。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowUseOfTheKey",
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:111122223333:key/e18455dd-bf55-4b0a-b0ab-XXXXXXX"
        }
    ]
}
```

どうでもよいけどキーポリシーはSidに空白許可なのに、IAMポリシーのSidは空白不可という仕様揺れの罠があるのでキーポリシーからコピペしてくる場合は注意。

あと、元のキーポリシーにあったGrant系の権限は、AWSのS3とか他のサービスでKMSに暗号化対応しているのに権限を委譲するのに必要なだけで、なくてもユーザが暗号化/復号化するだけならいらない。必要ならGrant系もよしなに与えて下さい。

# まとめ
というわけでKMSへのアクセスをIAMグループ単位で管理できるようになった。
KMSキーポリシーでIAMグループが指定できないよ問題にハマった人は試してみるとよいんじゃなかろうか。

# おまけ（Terraformコードサンプル）
AWSの設定は基本的にTerraformで管理してるんだけど、IAMポリシーのJSONにキーIDがハードコードされちゃうのイマイチなので変数参照にできないかなぁと思って調べたら、aws_iam_policy_documentを使うとよいらしい。
というわけで、以下に書いたサンプルコードはTerraformでKMSキー作った時に、アクセス権限の管理をIAMグループで管理する例になってるので、Terraform使ってるならこちらもどうぞ。

[TerraformでIAMポリシーのJSONに変数を埋めたい場合はaws_iam_policy_documentを使う](http://qiita.com/minamijoyo/items/d68f162bd29cd3d766e8)