CloudFormationで既存IAMロールのポリシーを自動で追加するにはどうしたら良いかを考えた

# はじめに
CloudFormationをやっていると大体ハマるのがIAM部分で、最小権限とか考え始めるとかなり頑張ってロールやポリシーの設計をしなければいけない。
CloudFormationで「こうやったらどう動くの？」が分かっていないと正しい設計もできないので、色々試してみる。

ゴールは「CloudFormationで自動構築されたリソースをIAMの最小権限付与のベストプラクティスに従って管理するにはどうやるのが良いか」が考えられることだ。もちろん、人力で管理するのは自動構築の意味がなくなってしまうので、あくまでもCloudFormationテンプレートの中で完結することを考える。

# 前提条件
- IAMをなんとなく理解している(グループ、ユーザ、ロール、ポリシーの違いが分かっていれば充分かな……)
- CloudFormationでIAM関連のリソースを作ったことがある

さすがにこの辺が分からないと読んでもサッパリだと思うので……

IAMへの理解を深めたい人は、BlackBeltオンラインセミナーをちゃんと観ておくと良い。
[【AWS Black Belt Online Seminar】AWS Identity and Access Management (AWS IAM) Part1](https://www.youtube.com/watch?v=K7F5yTThynw)
[【AWS Black Belt Online Seminar】AWS Identity and Access Management (AWS IAM) Part2](https://www.youtube.com/watch?v=qrZKKF6V6Dc)

あと、書籍なら『[AWSの薄い本　IAMのマニアックな話](https://www.amazon.co.jp/dp/B085PZCMG2)』あたりも分かりやすい。

# 実験① IAMのインラインポリシーへの追記はできるか
## まずはIAMロールとベースになるポリシーを作る。
以下の2つのCloudFormationテンプレートを順次実行して、IAMロールとベースになるポリシーを作ろう。省略しているが、それぞれ```AWSTemplateFormatVersion: "2010-09-09"```は先頭に記述すること。ポリシーに設定する値は今回は何でも良いのでテキトーに。

```YAML:01_IAMRole.yml
Resources:
  IAMPOLICY:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TestPolicy1
      Roles:
        - !ImportValue TestRoleName
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Sid: TestPolicySid2
            Effect: Allow
            Action:
              - "codepipeline:GetPipeline"
            Resource: "*"
```

```YAML:02_IAMPolicy_Add.yml
Resources:
  IAMPOLICY:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TestPolicy1
      Roles:
        - !ImportValue TestRoleName
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Sid: TestPolicySid1
            Effect: Allow
            Action:
              - "sns:Publish"
            Resource: "*"
```

さて、この状態でTestRoleForIAMのユーザにアタッチされているインラインポリシーを参照すると、以下のようになっている。まあ、そうなるように設定したのだから当たり前だ。

```JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sns:Publish"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Sid": "TestPolicySid1"
        }
    ]
}
```

## 別のCloudFormationのスタックから同一ポリシーを更新する
以下のCloudFormationテンプレートでスタックを作成して、TestPolicy1を更新してみる。
差分としては、Sidを変えていることと、許可するアクションを変えているところだ。
これをちゃんと差分として検知して追記してくれると良いのだが。

```YAML:03_IAMPolicy_Upd_AddSid.yml
Resources:
  IAMPOLICY:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TestPolicy1
      Roles:
        - !ImportValue TestRoleName
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Sid: TestPolicySid2
            Effect: Allow
            Action:
              - "codepipeline:GetPipeline"
            Resource: "*"
```

だがしかし、これを実行した後のTestPolicy1のインラインポリシーは以下の様になっていた。

```JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "codepipeline:GetPipeline"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Sid": "TestPolicySid2"
        }
    ]
}
```

つまりは、Sidを変えたところであくまでも**「上書き更新」**という動作になってしまうようだ。
しかも、**スタックを削除したとしてもこの上書きされた内容は戻らなかった**。インラインポリシーはバージョンの概念がないので元の値が何だったか、場合によっては分からなくなってしまうだろう。これは気を付けなければいけないポイントになりそう。

今回、インラインポリシー




で実験したから分からないが、管理ポリシーでやってみるとバージョンが上がって、削除時には戻るのだろうか。

# 実験② IAMの管理ポリシーの場合はどうか
というわけで、今度は管理ポリシーで試してみる。

```YAML:04_IAMManagedPolicy_Add.yml
Resources:
  IAMPOLICY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: TestManagedPolicy1
      Description: ManagedPolicy for test
      Roles:
        - !ImportValue TestRoleName
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Sid: TestManagedPolicySid1
            Effect: Allow
            Action:
              - "sns:Publish"
            Resource: "*"
```

で管理ポリシーを作成し、

```YAML:05_IAMManagedPolicy_Upd_AddSid.yml
Resources:
  IAMPOLICY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: TestManagedPolicy1
      Roles:
        - !ImportValue TestRoleName
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Sid: TestManagedPolicySid2
            Effect: Allow
            Action:
              - "codepipeline:GetPipeline"
            Resource: "*"
```

で更新を試みる。すると……

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/631e406c-6aec-3952-d508-892a0d2f93f5.png)

な、なんだってー！(AA略)

たしかに、ユーザーガイドを見てみると、以下のような差分があり、ManagedPolicyには更新の機能が無いようだ。

・AWS::IAM::Policy
> 指定の IAM ユーザー、グループ、またはロールに埋め込まれたインラインポリシードキュメントを追加または更新します。

・AWS::IAM::ManagedPolicy
> AWS アカウント用に新しい管理ポリシーを作成します。
> 
> このオペレーションでは、v1 のバージョン識別子を持つポリシーバージョンが作成され、ポリシーのデフォルトバージョンとして v1 が設定されます。ポリシーのバージョンの詳細については、IAM ユーザーガイドの「管理ポリシーのバージョニング」を参照してください。

つまりは、最小権限をCloudFormationの単独のポリシーのみで実現しようとすると、純粋な更新を行うのは難しいように思える(あえて絞った言い方をしたのは、CLIだったら実現できる可能性が高いため。未検証)。

# ではどうするか？
ここまでの検証結果から考えると、権限の付与を自動で行うのであれば、新しいポリシーを追加してロールやユーザにアタッチすることが効率が良いと考えられる。
そうした場合、今度は上限が気になってくる。

【AWS公式】[IAM および STS の制限](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_iam-limits.html)

このドキュメントを確認すると、

|リソース|デフォルトの制限|
|:--|:--|
|AWSアカウントのカスタマー管理ポリシー|1500|
|IAMロールにアタッチされた管理ポリシー|10|

とある。上限緩和は可能なようであるが、つまりはそんなに無闇にアタッチしてくれるなということのような気もする。
また、上記はあくまでも「管理ポリシー」なので、インラインポリシーの話はしていない。
では、インラインポリシはどれだけ追加できるだろうか？

# 実験③インラインポリシーを追加しまくる
↓あんまりちまちまとやるのも面倒なので、とりあえず一気に100くらいインラインポリシ追加してみるぞ！と思い以下のスクリプトを書いて流す。

```python
#!/usr/bin/python3

print('AWSTemplateFormatVersion: "2010-09-09"');
print('Description:');
print('  Test Policy for IAM');
print('');
print('Resources:');

for i in range(1,101):
  print('  IAMPOLICY%d:' % i);
  print('    Type: AWS::IAM::Policy');
  print('    Properties:');
  print('      PolicyName: TestPolicy%d' % i);
  print('      Roles:');
  print('        - !ImportValue TestRoleName');
  print('      PolicyDocument:');
  print('        Version: "2012-10-17"');
  print('        Statement:');
  print('          - Effect: Allow');
  print('            Action:');
  print('              - "sns:Publish"');
  print('            Resource: "*"');
```

よし！100個はいけた！実際どこまでいけるかは気になるものの、100あれば充分実用に耐え得るし、マニュアルに記載がないということはひとまず「どこまでも」いけるのだろう。

![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/144ebef0-fc8a-a82b-1f8f-51096a02a7f9.png)

# 結論
ということで、CloudFormationの中で自動で最小権限を管理するには、↑に書いたように

- **新しいポリシーを追加してロールやユーザにアタッチする**

が手間がかからずに対応することが可能であると考える。
多少冗長ではあるけど…。
CLIでPolicyDocumentを参照してきて編集してから書き込めばいいじゃんとかはあるかもしれないが、できれば使うサービスは一択で頑張りたいし……。そういう意味ではCDKならもう少し柔軟に対応できる気がしなくもないが、試していないので今回はここまで。