GitHub Actionsのトークンを使ってAWS公式のconfigure-aws-credentialsを使ってみた

2021/09/23に公開

20211127追記

下記のブログ記事にあるように、configure-aws-credentialsがOIDCでAssumeRoleできる機能を正式に取り込んだため、本記事に書いてあるような面倒なことをしなくても簡潔にAssumeRoleできるようになりました。
ついにaws-actions/configure-aws-credentials@v1にOIDCでAssumeRoleできる機能が取り込まれました!
そのおかげで、GitHub Actionsのワークフロー上では単に下記のようにすればAssumeRoleできます。

name: Example
on:
  push:

env:
  AWS_ROLE_ARN: <使いたいIAMロールのARN>

permissions:
  id-token: write
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: "${{ env.AWS_ROLE_ARN }}"
          aws-region: ap-northeast-1
          role-duration-seconds: 900
          role-session-name: GitHubActionsTestSession

もちろん、事前にAWSアカウント側でIAMのOIDCプロバイダを準備しておく必要があります(GitHub Actions OIDCでconfigure-aws-credentialsでAssumeRoleする)

以下はリリース前に試したことですので、参考までに。


始めに

先日、以下のツイートが話題になりました。

https://twitter.com/__steele/status/1437984026145427461?s=20

これにより、GitHub ActionsからAWSのリソースに触る必要のある時[1]にGitHub Actions実行用のアクセスキーが不要になります。

なぜそんなことができるのかについてざっくりいうと、IAMでIdプロバイダを作り、GitHub側のOIDCのIssuerを許可することによりGitHub側からの権限の呼び出しを行えるようにしています。
また、IAMロールの信頼関係の設定で呼び出し元リポジトリやOrganizationなどの名称で呼び出し元リポジトリを絞り込むことができます(後ろで補足します)。

IAMはもともとOIDC(OpenID Connect)に対応していて、WebID Federationによって外部のIdプロバイダと連携できます。
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_oidc.html

また、クラスメソッドさんでもさっそくブログが書かれています。

https://dev.classmethod.jp/articles/github-actions-without-permanent-credential/

IAM User / Access Keyがこの世に存在してよい理由の一つがなくなりました。

個人的には上記の文言に激しく同意したいです。
なぜアクセスキーはなるべく作らないほうが良いかについては、過去記事にしました。

https://zenn.dev/yutaro1985/articles/daa2aaa6cc9907#とりあえずiamユーザのアクセスキーを使わせる

本記事で試したこと

大元のブログでやっていたこと

元ブログや同じことをやってみた記事ではGitHub Actionsで下記のように権限を呼び出して、テストとしてaws sts get-caller-identityを実行しています。

# .github/workflows/example.yml
name: Example
on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - run: sleep 5 # there's still a race condition for now

      - name: Configure AWS
        run: |
          export AWS_ROLE_ARN=arn:aws:iam::0123456789012:role/ExampleGithubRole
          export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/awscreds
          export AWS_DEFAULT_REGION=us-east-1

          echo AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE >> $GITHUB_ENV
          echo AWS_ROLE_ARN=$AWS_ROLE_ARN >> $GITHUB_ENV
          echo AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION >> $GITHUB_ENV

          curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE

      - run: aws sts get-caller-identity # just an example. why not deploy something?

元ブログより抜粋)

id-tokenはこの記事を書いた時点ではまだドキュメントには記載がないオプションです

Configure AWSのところで何をしているかと言うと、まずGitHub Actionsの環境変数に
AWS_WEB_IDENTITY_TOKEN_FILE
AWS_ROLE_ARN
AWS_DEFAULT_REGION
という3つの環境変数をセットする設定を入れています。
awscliやSDKなどではどうやらAWS_WEB_IDENTITY_TOKEN_FILEにトークンファイルのファイルパス、AWS_ROLE_ARNに使いたいIAMロールのarnを設定しておくと、AWS_WEB_IDENTITY_TOKEN_FILEにあるトークンファイルを読み込んで、AWS_ROLE_ARNで設定したIAMロールをAssumeRoleWithWebIdentity[2]で呼び出して使用してくれるようです。

このことはAWS CLIのドキュメントにも記載されています(今回初めて知りました…)。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc

最後に
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL"
でGitHubに対してトークンを発行するためのリクエストを投げています。
$ACTIONS_ID_TOKEN_REQUEST_TOKEN$ACTIONS_ID_TOKEN_REQUEST_URLはGitHub Actionsの環境変数のようですが、執筆時点でドキュメントに記載はありませんが、GitHub Actionsが持っている$ACTIONS_ID_TOKEN_REQUEST_TOKENの値を使って$ACTIONS_ID_TOKEN_REQUEST_URLにリクエストを送り一時トークンを発行しているんだと思います。
※この辺はドキュメントに記載がなく、詳細がよくわからないので何かわかる方いたらぜひ補足をお願いします。もしドキュメントに何か追記されたらこの記事でも補足をします。

課題

さて、これでもAWSのロールを引き受けることができるのですが、いくつか問題があります。

など。
GitHub Actionsではconfigure-aws-credentialsを使ってクレデンシャルの設定を行ってからAWSのリソース周りをいじる、という流れで行っているものが多いので可能であればconfigure-aws-credentialsに乗りたいなあと思います。

クレデンシャルの設定についてはaws sts assume-role-with-web-identityをGitHub Actions上で実行させて、そこで発行されたAWSの一時キーとトークンを使用して以後の操作を行うようにしているものが多かったですが、試してみたらconfigure-aws-credentialsを利用することがどうやら可能なようでした。

やったこと

まずは通常通りにGitHubのトークンを取得します。
先に貼った定義の中ではConfigure AWSの部分でやっていたことですね。

まだきちんとは試せていないですが、トークンを発行するだけなのでここでは最低限

export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/awscreds
echo AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE >> $GITHUB_ENV
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" \
| jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE

だけ行えば十分だと思います。
ただ、環境変数にロール名を入れていて次に使うのであれば上で貼ったものと同様にここで定義しておきましょう(上記のような説明を書いたものの、この記事では環境変数をそのまま使用する例を以後記載します)。

その次に、configure-aws-credentialsを呼び出してAssumeRole[2:1]を行います。
ReadMeにはすべては書いてないのですが、action.ymlを見ると設定可能なオプションがすべて確認できます。
https://github.com/aws-actions/configure-aws-credentials/blob/master/action.yml

      - uses: aws-actions/configure-aws-credentials@master
        with:
          role-to-assume: "${{ env.AWS_ROLE_ARN }}"
          web-identity-token-file: "${{ env.AWS_WEB_IDENTITY_TOKEN_FILE }}"
          aws-region: "${{ env.AWS_DEFAULT_REGION }}"
          role-duration-seconds: 900
          role-session-name: GitHubActionsTestSession

web-identity-token-fileに、取得したトークンのパスを渡します。
そうすることでトークンファイルを使ってくれます。
WebID Federationの設定が済んでいれば、あとはrole-to-assumeに使用したいロールのarnを渡してあげればAssumeRoleWithWebIdentity[2:2]でロールを引き受けることができます。
role-duration-secondsでは、AssumeRole[2:3]したロールのクレデンシャルの有効期限を設定できます。
role-duration-secondsを設定しないとIAMロール側のMaxSessionDurationの設定時間(1時間=3600秒)をオーバーする値を使うらしくエラーになってしまうので、適切な期限を設定しましょう。
MaxSessionDurationを長くすることも可能ですが、GitHub Actionsをそんなに長時間実行し続けることはないと思うので必要と思う期限を設定すべきでしょう。
ちなみにAPIの仕様で900秒以上の時間を設定する必要があります。
role-session-nameは必須ではないですが、用途ごとにセッション名を分けておくとCloudTrailで追うときにどこでAssumeRole[2:4]されたかがトレースしやすくなるのでわかりやすい名前をつけておくことをお勧めします。

role-duration-secondsrole-session-nameのオプション設定の内容が有効であることはCloudTrailで確認できますので試したときに確認してみるとよいでしょう。

やったことの補足

  • ここでは設定した環境変数を使用した例を書いていますが、ここでしか設定しないと思われるので、前述のように環境変数をAWS_WEB_IDENTITY_TOKEN_FILEしか設定していないのであれば直書きでもよいでしょう。そのへんはお好みでどうぞ。
  • aws-regionには一応AWS_DEFAULT_REGIONの値を明示的に設定していますが、AWS_DEFAULT_REGIONが設定されていればほかにリージョンの指定がない場合AWS_DEFAULT_REGIONのリージョンを使用してくれるはずなので不要かもしれません。

その他

Bitbucketでは公式ドキュメントにほぼ同様のことをしている例が書いてあります。

https://support.atlassian.com/ja/bitbucket-cloud/docs/deploy-on-aws-using-bitbucket-pipelines-openid-connect/

しかしながらこちらにもoidc: trueという見慣れないオプションが設定されておりドキュメントには何も記載がされていません。
ドキュメントの整備が待たれますが、ソースのここを見ればわかる等がもしあったら補足をお願いします。

なぜこの方法がセキュアか

前述の通り、Idプロバイダを作成するときにGitHub Actions側のIssuerを設定しています。
また、使用したいIAMロールを作成する際に信頼関係の設定の部分のIAMポリシーで呼び出し元を制限しています。
これはAssumeRole[2:5]をする際に必ず行う設定です。

今回は下記のように設定しています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::xxxxxxxxxxxx:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": [
        "sts:TagSession",
        "sts:AssumeRoleWithWebIdentity"
      ],
      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:yutaro1985/github-actions-oidc-test:*"
        }
      }
    }
  ]
}

sts:TagSessionをつけてますが、不要だと思いますね…。

Principalのところは、たとえばあるIAMロールからAssumeRole[2:6]できるようにしたいのであれば

      "Principal": {"AWS": "arn:aws:iam::AWS-account-ID:role/role-name"},

のように書きます。
ほかにも設定可能なものは下記ドキュメントに載っています。
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_principal.html

また、Conditionのところで文字列一致によりリポジトリ名を使って呼び出し元を制限しています。
今回の記載の場合、yutaro1985/github-actions-oidc-testでしか使用ができません。

ここにたとえば
yutaro1985/*と設定すると、yutaro1985が管理するすべてのリポジトリで使えます。
また、Organizationでも絞ることができ、hogehogeというOrganizationのリポジトリすべてで利用できるようにするためには
"repo:hogehoge/*"とすれば可能です。
CfnやTerraformで作るならここの部分は変数化しておくとよいかもしれませんね。

ということで、該当するリポジトリのGitHub Actionsで発行したトークンだけがロールを引き受けることができるような設定をしているから安全に使うことができるということです。
万が一GitHub Actionsで発行したトークンが漏洩した場合でもロールのarnがバレなければ該当のロールを使うことはできません。
とはいえarnをソースに埋め込んでいたり、Actionsのログに残っていたりするケースもあるのでGitHub Actionsが発行したトークンが万が一漏洩するようなことがあった場合は注意が必要でしょう。

本記事を書くにあたり実験したこと

下記のスクラップに垂れ流しています。

https://zenn.dev/yutaro1985/scraps/b19fa829efda1f

記事を書くにあたりほかに参考にさせてもらった記事など

以下の記事とスクラップは自分が試したり確認したいなと思っていたことをほぼ全部先にやってくれていたのでとても助かりました。
最初のブログではCfnで書いてあるだけでしたが、手動で作成する場合についても試してくれています。

https://zenn.dev/mryhryki/articles/2021-09-19-access-aws-by-github-actions

https://zenn.dev/mryhryki/scraps/81d85c8e28af88

OIDCのIDトークンについては以下を参考にさせていただきました。

https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06

作ったもの

今回はTerraformで必要なものを作りました。

https://github.com/yutaro1985/github-actions-oidc-test

なお、すでに先駆者の方がいらっしゃったので大いに参考にさせていただきました(最初はまったく見ないで作ったのですがまさかリポジトリ名までまったく同じとは…)

https://github.com/takanabe/github-actions-oidc-test

今回説明したconfigure-aws-credentialsを使っている部分が違うほか、
自分は各設定の元に使用したのがクラスメソッドさんの記事の方なのでAudienceが定義してあったりします。

感想

これによって、GitHub Actionsにアクセスキーを渡す必要がなくなり、アクセスキーの管理の手間と漏洩のリスクが軽減できます。

OIDCを使ったロールの引受については今までやったことがなかったので勉強になりました。
OIDC周りについては単純にまだ理解不足な部分が多いのでもうちょっとちゃんと勉強したいなと思います。

脚注
  1. より厳密に言うと、AWSのAPIに対してリクエストする必要がある時 ↩︎

  2. AssumeRoleについては次の記事が参考になります。 https://dev.classmethod.jp/articles/iam-role-passrole-assumerole/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

GitHubで編集を提案

Discussion