Github ActionsからAWSにアクセスする際にリポジトリやブランチで動的なポリシー制御(ABAC)をする方法

# いきなりまとめ

- リポジトリやブランチごとにRoleやポリシーを定義したくない
- ABACとはタグを用いたアクセス制御
- [configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#session-tagging-and-name)を使用してGithub ActionsからOIDCでIAM Roleを使用する場合では、セッションにタグは付与されない
- 原因はAssumeRoleWithWebIdentity のAPIではタグ付与がされないことにある
- 一方でAssumeRole APIではタグ付与をサポートしているため、AssumeRoleWithWebIdentityで取得したIAM Role認証から更に、role-chainingで別のRoleへAssumeRoleすることでタグ付与が可能
  - [assumerole-with-role-previously-assumed-by-action-in-same-workflow](https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#assumerole-with-role-previously-assumed-by-action-in-same-workflow)
- タグ付与の際には `sts:TagSession` の権限付与が必要なため注意
- Branchの値は実際にはGITHUB_REFなため、単純なブランチ名ではないことに注意
  - `refs/heads/{BRANCH_NAME}` / `refs/pull/{ISSUE_NUMBER}/merge` などになる
  - https://dev.classmethod.jp/articles/how-to-get-a-ref-branch-within-a-workflow-execution-in-github-actions/#toc-

## 設定例 

GitHubアクション例
```yaml
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: us-east-2
        role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
        role-session-name: MySessionName
    - name: Configure other AWS Credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: us-east-2
        role-to-assume: arn:aws:iam::987654321000:role/my-second-role
        role-session-name: MySessionName
        role-chaining: true
```


OIDC Roleに設定するPolicy(GithubOIDCRole)
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Resource": "arn:aws:iam::XXXXXXXXXXXX:role/GithubABACRole"
        }
    ]
}
```

実際にアクションを行うRoleに設定するPolicy(GithubABACRole)
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::XXXXXXXXXXXX:role/GithubOIDCRole"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Condition": {}
        }
    ]
}
```

ABACを用いたPolicy

```json
{
	"Version": "2012-10-17",
	"Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::abac-test-bucket/${aws:PrincipalTag/Branch}/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::only-main-branch/*"
            ]
            "Condition": {
				"StringEquals": {
					"aws:PrincipalTag/Branch": "main"
				}
			}
        }
    ]
}
```

# 前提

- 基本的なOIDCを用いたGithub ActionsでのAWSへのアクセス方法は以下のページを参考にしてください
  - https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
  - https://qiita.com/satooshi/items/0c2f5a0e2b64a1d9a4b3
  - https://qiita.com/kyoF/items/c1e99f9388a36ed9bfc1

# リポジトリやブランチごとにRoleやポリシーを定義したくない

OIDC便利ですよね。IAM Userを使っていたころには戻れません

信頼ポリシーで指定のリポジトリ・ブランチのみで使用可能なRoleを作成することも可能です

https://docs.github.com/ja/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#configuring-the-role-and-trust-policy

https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment#deployment-branches-and-tags

一方で、mainブランチとトピックブランチでS3にファイルをアップロード/削除する処理があるとして、それを1つのIAM Roleで実施する場合、トピックブランチの変更によってmainブランチでアップロードされたS3ファイルの変更ができてしまいます。

なので、前述の信頼ポリシーを設定するのですが、これをしていくと制御したいブランチごとにRoleが必要になります。
これが複数リポジトリあれば、さらに増えていきます

なので、前述の制御をIAM Policyで使用できたらと思うのですが、それは使用できません


# ABACとはタグを用いたアクセス制御

こういった用途と言えば、ABACです。
ResourceやCondition内でタグの値を使用できるので、それを使って制御しようというものです

主に[aws:PrincipalTag](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principaltag)を使って制御をしていきます

ABACの詳細については以下の記事を参考にしてください

https://dev.classmethod.jp/articles/aws-abac-tips/

https://docs.aws.amazon.com/ja_jp/ja_jp/IAM/latest/UserGuide/introduction_attribute-based-access-control.html




# Github Actionsでタグ付与自体はサポートしているようだが。。。？

[configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#session-tagging-and-name)を見ると、どうやらセッションタグを付与しているようです


>The default session name is "GitHubActions", and you can modify it by specifying
the desired name in `role-session-name`. The session will be tagged with the
following tags: (Refer to [GitHub's documentation for `GITHUB_` environment
variable definitions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables))

| Key        | Value             |
| ---------- | ----------------- |
| GitHub     | "Actions"         |
| Repository | GITHUB_REPOSITORY |
| Workflow   | GITHUB_WORKFLOW   |
| Action     | GITHUB_ACTION     |
| Actor      | GITHUB_ACTOR      |
| Branch     | GITHUB_REF        |
| Commit     | GITHUB_SHA        |

ドキュメントでもAssumeRole時にタグ付与が可能で、それを使用した制御が書かれています

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_session-tags.html

しかし、以下のような不穏な記述があります



>The action will use session tagging by default during role assumption, unless you follow our recommendation and are assuming a role with a WebIdentity. 
For WebIdentity role assumption, the session tags have to be included in the encoded WebIdentity token. 
This means that tags can only be supplied by the OIDC provider, and they cannot set during the AssumeRoleWithWebIdentity API call within the Action. See [#419](https://github.com/aws-actions/configure-aws-credentials/issues/419) for more information.


要するにAssumeRoleWithWebIdentityのAPIではタグ付与はされないということのようです。

そしてOIDCでAWSにアクセスする際に使用するAPIはAssumeRoleWithWebIdentityです。

実際に試してみましたが、タグ付与はされず、当然ながらaws:PrincipalTagも機能しません

# AssumeRole APIを呼び出せば使用可能

AssumeRoleWithWebIdentityではできないだけで、AssumeRoleであればできそうです。

なので、実際に試したところタグ付与の確認とそれを使用したaws:PrincipalTagによる制御が可能でした

いきなりworkflowからAssumeRoleはできないので、まずはAssumeRoleWithWebIdentityで権限を取得し、そのIAMを使用してさらにAssumeRoleをします。

いわゆる[ロールの連鎖](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html#iam-term-role-chaining)を使用したアプローチになります

configure-aws-credentialsでも`role-chaining: true`を設定することで、ロール連鎖が可能です

GitHubアクション例
```yaml
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: us-east-2
        role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role
        role-session-name: MySessionName
    - name: Configure other AWS Credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: us-east-2
        role-to-assume: arn:aws:iam::987654321000:role/my-second-role
        role-session-name: MySessionName
        role-chaining: true
```

タグ付与の際には sts:TagSession の権限付与が必要になります。AssumeRoleを許可だけではできないため注意してください。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required

OIDC用のRoleにはポリシーを作成してアクションを許可。実際にアクションを行うRole側には信頼ポリシーにて許可を行ってください

OIDC Roleに設定するPolicy(GithubOIDCRole)
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Resource": "arn:aws:iam::XXXXXXXXXXXX:role/GithubABACRole"
        }
    ]
}
```

実際にアクションを行うRoleに設定するPolicy(GithubABACRole)
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::XXXXXXXXXXXX:role/GithubOIDCRole"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Condition": {}
        }
    ]
}
```


実際に付与されるタグの例(Cloudtrailで確認)

```json
    "requestParameters": {
        "roleArn": "arn:aws:iam::XXXXXXXXXXXX:role/GithubABACRole",
        "roleSessionName": "abac-test",
        "durationSeconds": 3600,
        "tags": [
            {
                "key": "GitHub",
                "value": "Actions"
            },
            {
                "key": "Repository",
                "value": "abac-test-repo"
            },
            {
                "key": "Workflow",
                "value": "Test Shipment"
            },
            {
                "key": "Action",
                "value": "__aws-actions_configure-aws-credentials_2"
            },
            {
                "key": "Actor",
                "value": "willco21"
            },
            {
                "key": "Commit",
                "value": "43326e48da8207167a3f75bda09xxxxxxxxxxxx"
            },
            {
                "key": "Branch",
                "value": "refs/pull/51/merge"
            }
        ]
```





# 実際の活用例

再掲ですが、付与されるタグは以下になります。

また、 Branchの値は実際にはGITHUB_REFなため、単純なブランチ名ではないことに注意が必要です
具体的には以下の記事を参考にしてください

https://dev.classmethod.jp/articles/how-to-get-a-ref-branch-within-a-workflow-execution-in-github-actions/#toc-

| Key        | Value             |
| ---------- | ----------------- |
| GitHub     | "Actions"         |
| Repository | GITHUB_REPOSITORY |
| Workflow   | GITHUB_WORKFLOW   |
| Action     | GITHUB_ACTION     |
| Actor      | GITHUB_ACTOR      |
| Branch     | GITHUB_REF        |
| Commit     | GITHUB_SHA        |

そのため基本的にはRepositoryとBranchを用いた以下のようなポリシー設定が考えられます

## S3へのアップロード制御

一番よくある利用例だと思います。

Resourceのパスに `/${aws:PrincipalTag/Branch}`を使用することでそのブランチ用のPathだけにアクセスできるように設定できます

また、mainブランチのみ別のアクションを許可した場合などでも、Conditionを使用することで容易に制御が可能です

```json
{
	"Version": "2012-10-17",
	"Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::abac-test-bucket/${aws:PrincipalTag/Branch}/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::only-main-branch/*"
            ]
            "Condition": {
				"StringEquals": {
					"aws:PrincipalTag/Branch": "main"
				}
			}
        }
    ]
}
```

リポジトリごとにバケットを分けているならば、以下のようなポリシーを1つ設定することで、1つのRoleで多くのリポジトリのworkflowでの操作を許可することも可能です（かなり汎用的なポリシーになるのでオススメはしません）

```json
{
	"Version": "2012-10-17",
	"Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::${aws:PrincipalTag/Repository}/${aws:PrincipalTag/Branch}/*"
            ]
        }
    ]
}
```


## aws:ResourceTagを用いたリソース操作の制御

aws:ResourceTagを用いることで、特定のタグの値を確認して該当するリソースのみにアクションを制御できます

workflowでEC2の操作をする場合において、自身のBranchのタグが追加リソースのみに制限することで、意図しないEC2への操作を防ぐことが可能です


```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "StringEquals": {"aws:ResourceTag/Branch": "${aws:ResourceTag/Branch}"}
            }
        },
        {
            "Effect": "Allow",
            "Action": "ec2:DescribeInstances",
            "Resource": "*"
        }
    ]
}
```

# まとめ

Github ActionsからAWSにアクセスする際にもABACを用いた制御が可能なことがわかりました

設定されるタグの関係上、事前にアクションを実行するリソースの設計が必要にはなりますが、うまく活用することで大量のIAM RoleやPolicy管理からの解放ができると思います

また、シンプルにAssumeRoleWithWebIdentityでのタグ付与が早く実現してくれたらなと思います