GitHub Actions OIDCを活用してTerraform CIをセキュアにする話

はじめに

こんにちは, 基盤開発チームの奥山(okue)です.

基盤開発チームでは,

tech.high-link.co.jp

のように, サービス開発の基盤に関する開発を行っています.

カラリアでは, AWS 上のインフラ構築に Terraform を利用しており, 2種類の CI を GitHub Actions で実装しています.

  • Pull Request が作成されたら terraform plan をする
  • Pull Request が main ブランチにマージされたら terraform apply をする

本記事では, GitHub Actions OIDC を用いて, Terrafrom CI をセキュアに構築した話をします.

強い権限の IAM ユーザーのアクセスキーを GitHub Actions に登録してしまうことによる問題

まずはじめに, Terrafrom CI を構築する際にセキュリティや権限的に問題になったことをお話します.

前提

GitHub Actions で AWS クレデンシャルの設定には, aws-actions/configure-aws-credentials を利用しています.

CI 用の IAM ユーザーを作って, GitHub Actions のシークレットに登録し, 下のような設定で運用している事例は少なくないと思います.

- name: 'Configure AWS credentials'
  uses: 'aws-actions/configure-aws-credentials@v1'
  with:
    aws-access-key-id: '${{ secrets.AWS_ACCESS_KEY_ID }}'
    aws-secret-access-key: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'

IAM ユーザーの権限が弱ければ大きな問題はないですが, 権限が強いと次の2つが問題となります.

  1. アクセスキーの漏洩リスク
  2. レポジトリに write 権限のある (≒ commit できる) 開発者であれば GitHub Actions に登録された IAM ユーザーの権限を利用可能になってしまう

漏洩リスク

terraform apply のようにインフラを変更できる程の強い権限の IAM ユーザーであれば, 尚更危険です. また, このリスクを緩和するために, キーをローテーションする必要性も発生します.

Write 権限のある開発者が IAM ユーザーの権限を利用できる

GitHub ドキュメントの

You can use and read encrypted secrets in a workflow file if you have access to edit the file.
引用元: Encrypted secrets - GitHub Docs

という記載から分かるように, レポジトリに write access できる人であれば誰でも GitHub Actions のシークレットを使えてしまいます.

例えば, 下のようにワークフローを書き換えて, 新しく作成した my_working_branch に commit すれば, GitHub Actions に登録された強い権限の IAM ユーザーを利用して AWS の操作ができてしまいます.

on:
  push:
    branches:
      - 'my_working_branch'
...
  steps:
    - name: '強い権限の access key を使う'
      run: './run_something ${{ secrets.AWS_SECRET_ACCESS_KEY }}'

開発担当者が予期せず強い権限で AWS を操作してしまったり, アクセスキーを漏洩してしまうといった事故を防ぐため, IAM ユーザーのアクセスキーを GitHub Actions シークレットに登録することは避けたいです.

そこで私たちは, 強い権限を安全に運用するために GitHub Actions OIDC を活用することにしました.

GitHub Actions OIDC とは

どのように利用したかの話をする前に, GitHub Actions OIDC について簡単に紹介します.

OIDC ってなに?

認証のための仕様です. 次の記事が詳しく書かれていて分かりやすいので, そちらを参照してください.

qiita.com qiita.com

GitHub Actions OIDC 認証で AWS のリソースにアクセスできる

GitHub Actions OIDC を用いることで, シークレットに IAM ユーザーのクレデンシャルを登録することなく, GitHub Actions が AWS のリソースにアクセスできるようになります. ざっくりと, 下のような流れになります.

  1. IAM ロールと GitHub Actions ワークフロー間の信頼関係を設定する.
  2. GitHub Actions ワークフローのジョブ毎に OIDC トークンが発行される.
  3. ジョブ内のステップやアクションは OIDC トークンを GitHub Actions から取得し, AWS へ送信する.
  4. AWS は OIDC トークンを検証し, そのジョブのみが利用できる短命なアクセストークンを発行して GitHub に送信する.

GitHub Actions OIDC による細かい権限管理

GitHub Actions OIDC を使うメリットの一つとして, 公式ドキュメントには

You have more granular control over how workflows can use credentials
引用元: OpenID Connect を使ったセキュリティ強化について - GitHub Docs

とあり, ワークフロー単位での細かい権限管理が挙げられています. これこそが今回私たちが GitHub Actions OIDC を使う大きなモチベーションであり, 強い権限を安全に運用することを実現してくれました.

どうして細かい権限管理が可能になるのかは, GitHub Actions が発行する OIDC トークン (JWT) の形式を見れば分かります. OIDC トークンのペイロードは次のような claim を持っています.

{
  "sub": "repo:octo-org/octo-repo:ref:refs/heads/demo-branch",
  "aud": "https://github.com/octo-org",
  "ref": "refs/heads/main",
  "repository": "octo-org/octo-repo",
  "actor": "octocat",
  "workflow": "example-workflow",
  "event_name": "workflow_dispatch",
  "iss": "https://token.actions.githubusercontent.com",
  ...
  "nbf": 1632492967,
  "exp": 1632493867,
  "iat": 1632493567
}

(いくつかの claim は省略しているので, 正確な claim 一覧についてはこちらをご覧ください)

実行されたワークフローのレポジトリやワークフロー名, イベント名等が分かるようになっています. そして, ここで一番有益なのは subject claim (sub) でしょう.

Subject claim は, ワークフローをトリガーしたイベントに関する情報を含みます. 例えば, orgName/repoName というレポジトリの branchName ブランチへの push がされると repo:<orgName/repoName>:ref:refs/heads/<branchName> という文字列が subject となります.

他にも, プルリクエスト, タグ作成などの判別も可能です.

ワークフローをトリガーする event と subject claim の構文の例

Trigger event Syntax of subject claim
Pull request repo:<orgName/repoName>:pull_request
Push repo:<orgName/repoName>:ref:refs/heads/<branchName>
Tag repo:<orgName/repoName>:ref:refs/tags/<tagName>

(他のイベントについては, subject 要求の例 - GitHub Docs を参照してください)

お気づきの通り, subject claim 等の情報を使うことで,

  • main ブランチへ push された時にトリガーされたワークフローであるか
  • プルリクエストが作成された時にトリガーされたワークフローであるか

といったことを, AWS 側で判別できるようになり, ワークフロー単位での細かい権限管理が可能になります.

GitHub Actions OIDC を活用して CI を構築する

GitHub Actions OIDC についての前情報はこれで充分です.

ここからは,

  • PR が作成されたら terraform plan をする
  • PR がマージされたら terraform apply をする

という CI を GitHub Actions にセキュアに構築する際の設定の流れとポイントを紹介していきます.

ここでセキュアとは, terraform apply するワークフローのみが, terraform apply に必要な強い権限を利用できるように制限する, という意味です.

全体としては, 次のように AWS, GitHub を設定していきます.

step component todo
1 AWS AWS の IAM に ID プロバイダとして GitHub Actions を登録.
2 AWS terraform plan に必要な権限を持った IAM ロールを作成.
3 AWS terraform apply に必要な権限を持った IAM ロールを作成.
4 AWS それぞれの IAM ロールの信頼関係を適切に設定.
5 GitHub GitHub Actions ワークフローを "Assume Role directly using GitHub OIDC provider" するように設定.
6 GitHub GitHub の Branch protection rule で main ブランチへの直 push を禁止にして, approve 必須に変更.

1. AWS の IAM に ID プロバイダとして GitHub Actions を登録

公式ドキュメントに記載されてる通りに, ID プロバイダとして GitHub Actions を登録します.

2-3. terraform plan/apply に必要な権限を持った IAM ロールを作成

  • terraform plan に必要な権限 (= 弱い権限) を持った IAM ロールを作成します.
  • terraform apply に必要な権限 (= 強い権限) を持った IAM ロールを作成します.

4. 💡 それぞれの IAM ロールの信頼関係を適切に設定

ステップ 2-3 で作成した IAM ロールについて, どのアカウントがそのロールを使えるか (= assume-role できるか) という信頼関係を設定します.

先述の通り, GItHub Actions が発行する OIDC トークンの subject claim は, そのワークフローをトリガーしたイベントに関する情報を表していました.

IAM ロールの信頼関係の Condition で, OIDC トークンの subject claim をチェックすることにより,

  • terraform plan で利用する弱い権限のロールは, そのレポジトリ内のワークフローであれば利用できる
  • terraform apply で利用する強い権限のロールは, main ブランチへの push によるワークフローのみ利用できる

という挙動が実現できます.

以下は, 弱い権限の信頼関係の設定例です.

Condition.StringLikerepo:<orgName>/<repoName>:* とすることで, このレポジトリの任意のイベント起因のワークフローがこのロールを利用することを許可しています.

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:<orgName>/<repoName>:*",
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
    }
  }
}

次は, 強い権限の信頼関係の設定例です.

Condition.StringEqualsrepo:<orgName>/<repoName>:ref:refs/heads/main とすることで, このレポジトリの main ブランチへの push 起因のワークフローがこのロールを利用することを許可しています.

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:sub": "repo:<orgName>/<repoName>:ref:refs/heads/main",
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
    }
  }
}

IAM ロールの信頼関係の設定で, Organization, repository に関するチェックは忘れないようにしましょう. 他人のレポジトリのワークフローを認証してしまうことになり得ます.

5. GitHub Actions ワークフローを assume-role using GitHub OIDC するように設定

GitHub のドキュメントに従って, assume-role using GitHub OIDC するようにします.

permissions:
  id-token: 'write'
  contents: 'read'
  issues: 'write'
  pull-requests: 'write'
steps:
...
- name: 'Configure AWS credentials'
  uses: 'aws-actions/configure-aws-credentials@v1'
  with:
    role-to-assume: 'arn:aws:iam::123456789012:role/gh-action-tf-plan'
    role-session-name: 'github-action-terraform-plan'
...

OIDC トークン利用するために, permissions で id-token: write とする必要があるのですが,

これらのスコープのいずれかのアクセスを指定した場合、指定されたなかったものはすべてnoneに設定されます。
引用元: 権限をジョブに割り当てる - GitHub Docs

という仕様のため, 必要な権限は明示的に指定してあげる必要が出てきます.

私たちの場合は, contents, issues, pull-requests 権限の明示が必要でした.

6. GitHub レポジトリの branch protection rule を修正

強い権限の信頼関係では, OIDC トークンの subject claim が repo:<orgName>/<repoName>:ref:refs/heads/main であるかをチェックしていました.

誰でも main ブランチへ push できたら, 誰でも強い権限を行使できてしまうので, branch protection rule で

  • main ブランチへの直接 push することの禁止
  • Pull request のマージに1人以上の approve の必須化

を設定します.

これにより, terraform apply できる強い権限を行使できるのは, プルリクエストが approve されマージされた時のワークフローのみに制限できました.

おわりに

GitHub Actions OIDC を活用して, Terraform plan/apply する CI をセキュアな形で構築する話でした.

OIDC トークンの subject claim をチェックすることでワークフロー単位で柔軟な権限管理ができます. Terrafrom に限らず GitHub Actions で様々な CI/CD を構築する際に役立つので, 活用していただけたらと思います.

High Link の基盤開発チームでは, このようにシステムの開発だけでなく CI/CD といった DevOps の改善も行っています. 興味がある方はぜひカジュアルにお話をするだけでも歓迎です. 詳細は下記のリンクからどうぞ!

https://herp.careers/v1/highlink/o1UmuTrypU5_?utm_source=hatena&utm_medium=highlinktechblog&utm_campaign=GitHub.Actions.OIDCherp.careers