GitHub に AWS 認証情報を持たせずに、Actions で S3 Backend な Terraform の plan を実行する

この記事は、[Classi developers Advent Calendar 2021](https://adventar.org/calendars/6766) 2日目の記事です。

## はじめに

正式アナウンスに先んじて [Twitter で話題](https://twitter.com/__steele/status/1437984026145427461) になっていた、**GitHub Actions で AssumeRole が使える** 件が、2021年10月末頃に GitHub 社より正式にアナウンスされました。
[GitHub Actions: Secure cloud deployments with OpenID Connect](https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/)
(正確には、OpenID Connect による認証をサポートした、という内容です。)

Classi では、ソースコード管理に GitHub を使っていて、インフラ構成管理には Terraform を利用しています。
Terraform は、Backend に Amazon S3 を指定することで、Terraform によるインフラ構成管理をチームでできるようにしています。

今回、この正式アナウンスを機に、GitHub 側に AWS の認証情報(アクセスキー)を持たせることなく、GitHub Actions で S3 Backend な Terraform の plan を実行できるようにしたので、その内容を紹介します。

## 前提
### 想定読者

- AWS IAM User のアクセスキーを発行・管理せずに GitHub Actions から AWS リソースへアクセスしたい方
- Terraform を GitHub Actions で実行させたい方

### この記事に書かれていること

- GitHub Actions で AssumeRole を利用するための AWS の設定手順
- GitHub Actions で terraform plan を実行する Actions のサンプル

### この記事に書かれていないこと

- GitHub や GitHub Actions そのものの説明
- Terraform そのものの説明
- AWS や IAM そのものの説明
- OIDC(OpenID Connect) の説明

## 手順
基本的には [GitHub 公式の手順](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) に従って設定を進めるだけですが、ところどころ説明が端折られているので、やったことを順に記載していきます。

### AWS 側の設定手順

#### ID Provider の作成
[AWS 公式の手順](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_create_oidc.html)

- [IAM ID プロバイダのコンソール](https://console.aws.amazon.com/iamv2/home?#/identity_providers)に行き、「プロバイダを追加」をクリック
- プロバイダのタイプ: `OpenID Connect` を選択
- プロバイダの URL: `https://token.actions.githubusercontent.com`
    - URL 入力後、「サムプリントを取得」をクリックしてサムプリント取得
- 対象者(Audience): `sts.amazonaws.com`
    - 公式の Action [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) を使う場合は対象者に `sts.amazonaws.com` を指定
    - 今回は公式 Action を使ったのでこの通り指定
- タグは任意に設定
- 入力後はこんな感じ
    - ![スクリーンショット 2021-11-02 19.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75954/53ac78fc-96fc-efb8-c6db-5c8d9b4a4d2b.png)

#### IAM ポリシーの作成
[GitHub 公式の手順](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) では IAM ポリシーの話は出てきませんが、Terraform が S3 にアクセスするのに権限設定が必要なため、以下の IAM ポリシーを作成します。(IAM ロールのインラインポリシーでも構いません)

- [IAM ポリシーのコンソール](https://console.aws.amazon.com/iamv2/home#/policies)に行き、「ポリシーを作成」をクリック
- JSON エディタに切り替えて↓の json を貼り付け
- タグ、ポリシー名、説明は任意に設定

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::{YOUR_BUCKET_NAME}"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObjectAcl",
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::{YOUR_BUCKET_NAME}/*"
        }
    ]
}
```

#### IAM ロールの作成
GitHub Actions から利用する IAM ロールを作成します。

- [IAM ロールのコンソール](https://console.aws.amazon.com/iamv2/home#/roles)に行き、「ロールを作成」をクリック
- `ウェブ ID` を選択
    - ID プロバイダー: 最初に作成した `token.actions.githubusercontent.com:aud` を選択
    - Audience: `sts.amazonaws.com` を選択
    - ![スクリーンショット 2021-11-02 20.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75954/20c7e6dd-a4ea-8784-a5da-414f957ff4e4.png)
- アクセス権限
    - ↑で作成したポリシーを選択
- タグ、ロール名、説明は任意に設定

#### 信頼ポリシーの修正
[GitHub 公式の手順](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#configuring-the-role-and-trust-policy) によると、信頼ポリシーにはデフォルトでは Condition に `aud` しか入ってないので `sub` も追加する必要があると書いてあります。

> By default, the validation only includes the audience (aud) condition, so you must manually add a subject (sub) condition.
> Edit the trust relationship to add the sub field to the validation conditions.

sub を指定することで、リポジトリやブランチ単位で制限をかけることができるようです。
今回、弊社ではリポジトリを絞りたかったため、Condition に以下を追加しました。

```json
"StringLike": {
  "token.actions.githubusercontent.com:sub": "repo:{YOUR_ORGANIZATION_NAME}/{YOUR_REPOSITORY_NAME}:*"
}
```

- `{YOUR_ORGANIZATION_NAME}` には、Organization 名を指定
- `{YOUR_REPOSITORY_NAME}` には、この IAM ロールを引き受けることを許可するリポジトリを指定

※ `{YOUR_ORGANIZATION_NAME}` は Organization ではなく通常アカウントの指定でも使えると思うが未検証。

追加後の信頼ポリシー json 全体はこちらです。

```json
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::{YOUR_AWS_ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:{YOUR_ORGANIZATION_NAME}/{YOUR_REPOSITORY_NAME}:*"
        }
      }
    }
  ]
}
```

信頼ポリシーを上記のように修正して IAM ロールの作成は完了。

### GitHub Actions の用意

今回は、プルリクエストに `terraform plan {TARGET_DIRECTORY}` というコメントをした場合に、`{TARGET_DIRECTORY}` 配下で `terraform plan` を実行したかったため、それを実現する Action を用意しました。
実際に使っている yaml ファイルを一部加工したものがこちらです。

```yaml
# プルリクのコメントに反応して plan を実行する action
name: 'terraform plan'

on:
  issue_comment:
    types: [created, edited]

jobs:
  plan:
    name: 'terraform plan'
    # プルリクエスト上で "terraform plan " から始まるコメントをした場合にのみ実行
    if: contains(github.event.comment.html_url, '/pull/') && startsWith(github.event.comment.body, 'terraform plan ')
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: get branch
        id: get_branch
        run: |
          echo "::set-output name=branch::$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" ${{ github.event.issue.pull_request.url }} | jq -r '.head.ref')"
      - uses: actions/checkout@v2
        with:
          ref: ${{ steps.get_branch.outputs.branch }}
      - name: get working directory
        id: work_dir
        run: |
          echo "::set-output name=dir::$(echo "${{ github.event.comment.body }}" | sed 's/terraform plan //g')"
      - name: configure aws credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: {作成した IAM ロールの ARN}
          role-session-name: {作成した IAM ロールの名前}
          aws-region: {デフォルトリージョン}
      - uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: "1.0.0"
      - name: terraform init
        run: terraform init
        working-directory: ${{ steps.work_dir.outputs.dir }}
      - name: terraform plan
        continue-on-error: true
        id: plan
        run: terraform plan -no-color -lock=false
        working-directory: ${{ steps.work_dir.outputs.dir }}
      - name: Comment the plan result to the pull request
        uses: actions/github-script@v1
        env:
          STDOUT: "${{ steps.plan.outputs.stdout }}"
          TARGET_DIR: "${{ steps.work_dir.outputs.dir }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: process.env.TARGET_DIR + " terraform plan\n\n<details>\n<summary>result</summary>\n\n```\n" + process.env.STDOUT + "\n```\n\n</details>"
            })
```

ポイントは、以下の2点。

- permissions での `id-token: write` 設定
    - [GitHub 公式の手順](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-permissions-settings) でも記載のある通り、この設定を追加する必要がある
- 公式の Action [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) は `v1` または `v1.6.0` または `master`(ブランチ) を指定
   - OIDC に対応したのは [2021年11月23日にリリース](https://github.com/aws-actions/configure-aws-credentials/blob/v1.6.0/CHANGELOG.md) された `v1.6.0` からである
   - そのため、`v1.6.0` または、最新の v1.x.x を追いかけている `v1` または、常に最新である `master` ブランチを指定する必要がある
   - 本稿執筆時点での [GitHub 公式の手順](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#requesting-the-access-token) では `master` を指定しているが、これは、GitHub 社による正式アナウンス時点で [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) の最新版(OIDC対応版)が未リリースだったためと思われる

もし、最後のステップの「Comment the plan result to the pull request」が `Resource not accessible by integration` というエラーで失敗する場合は、Personal Access Token を利用してみてください。
こちらの記事が参考になります。

https://qiita.com/athlaliel/items/cfef41949f62638c13da

## おわりに

今回は terraform plan を実行するための Action や IAM ポリシーの紹介でしたが、やりたいことに合わせてポリシーの中身の修正と Action の修正で、他の AWS リソースへのアクセスを実現することができます。

ご参考まで。