AWS CDKでカスタムIAMポリシーを作成する

## 目的
AWS CodeCommitのカスタマーマネージドポリシーを参考に、AWS CDKで個人開発した内容を記事とします。

下記を満たす実装を目指します。
* 指定した IP アドレス範囲から接続するユーザーにリポジトリへのアクセスを許可する
* ブランチに対するアクションを許可または拒否する

https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/customer-managed-policies.html


## 何をつくるか
CodeCommitを利用可能な2つのユーザグループを作成し、下記制限を満たすカスタムIAMポリシーをアタッチします。
* 管理ユーザグループ：
    * CodeCommitに対するFullAccess権限をもつが下記を制限する
        * 接続元のIPアドレスを制限
* 一般ユーザグループ：
    * CodeCommitに対するPowerUser権限をもつが下記を制限する
        * 接続元のIPアドレスを制限
        * MASTERブランチへのプッシュおよびマージを拒否

### 動作環境
``` sh
$ python -V
Python 3.8.10

$ cdk --version
2.95.1

$ cat requirements.txt 
aws-cdk.core==1.204.0
aws-cdk.aws-iam==1.204.0

```
### 設計とディレクトリ構造
ベースとなるポリシーは公式サイトから取得し、jsonファイルとしてCDK内でLoadする設計としました。このポリシーを基礎としてカスタムIAMポリシーを実装しました。

https://docs.aws.amazon.com/ja_jp/aws-managed-policy/latest/reference/AWSCodeCommitFullAccess.html

https://docs.aws.amazon.com/ja_jp/aws-managed-policy/latest/reference/AWSCodeCommitPowerUser.html

```sh
$ tree
.
├── app.py
├── cdk.json
├── custom_iam_policy.py
├── json
│   ├── PolicyBasedOnAWSCodeCommitFullAccess.json
│   └── PolicyBasedOnAWSCodeCommitPowerUser.json
├── requirements.txt
```

### 接続元のIP制限
両グループ共通して接続元のIPアドレスの制限を実装したく、CDK内で読み込むjsonファイル内でConditionブロックを作成しました。

またResourceブロックの初期値はブランクとし、操作対象とするリポジトリの情報をCDKプログラム実行時に引数として渡せる実装としました。
``` json
"Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "codecommit:*"
      ],
      "Resource": "",
      "Condition": {
          "IpAddress": {
             "aws:SourceIp": [
              "制限するIPアドレス：XXXXXXXXXXXX"
            ]
          }
      }
    },
    ...
]
```

### MASTERブランチへのプッシュ／マージを拒否
一般ユーザグループに対してのみこの制限をアタッチしたく、下記のような実装を行っています。

``` python
# policyをアタッチ
# custom_policy：masterへのプッシュ・マージを拒否 一般のみ 
custom_policy_deny_action = iam.Policy(
    self,
    id="id_custom_policy_deny_action_for_master_branch",
    policy_name="CustomPolicyDenyActionForMasterBranch",
    groups=[user_group],# 一般ユーザグループを指定
    statements=[
        iam.PolicyStatement(
            effect=iam.Effect.DENY,
            actions=[
                "codecommit:GitPush",
                "codecommit:DeleteBranch",
                "codecommit:PutFile",
                "codecommit:Merge*",
            ],
            resources = [repository_arn], # 対象のリポジトリarn
            conditions={
                "StringEqualsIfExists": {
                    "codecommit:References": [
                        "refs/heads/main", 
                        "refs/heads/master",
                    ]
                },
                "Null": {
                    "codecommit:References": "false"
                }
            }
        )
    ]
)
```

## カスタムIAMポリシーの挙動確認結果
接続元IPアドレスの制限は、両グループとも期待通りの結果となりました。
``` sh
$ git pull
fatal: unable to access 'https://git-codecommitXXXXXXXXX': The requested URL returned error: 403
```
一般ユーザ向けのマスターブランチへのプッシュを拒否するポリシーの挙動も期待通りでした。
``` sh
$ git push origin master:master
To https://git-codecommitXXXXXXXXX
 ! [remote rejected] master -> master (You don't have permission to push changes to this branch.)
error: failed to push some refs to 'https://git-codecommitXXXXXXXXX'
```

## 開発してみて
IaCのよさについて、個人的には再現性・再利用性が魅力だと感じています。
* コードさえあれば誰でも同じ環境を構築可能
* 今回のケースでは、対象とするリポジトリを動的に変更可能な設計とすることで、同じような権限を他のリポジトリにも適用可能

## 最後に
IP制限やマスターブランチへのPush/Mergeの拒否といった制限はよくあるケースだと思います。
この記事がどれほど小さくとも、どなたかのお役に立てれば嬉しいです。