GitHub Actions のIDトークンを使ってAWSリソースにアクセスする

:::message alert
この記事は GitHub から公式にアナウンスされていない機能を使っています。
今後変更されたり使えなくなるなどの可能性がありますのでご注意ください。
:::

追記(2021-11-01)

正式にリリースされたようです!🎉🎉🎉

https://www.publickey1.jp/blog/21/github_actionsopenid_connectgithub.html

追記(2021-10-13)

Issuer が https://vstoken.actions.githubusercontent.com から https://token.actions.githubusercontent.com に変更になったようです。

https://twitter.com/toricls/status/1445990439060836355

まだ GitHub から何もアナウンスがされていない機能なのでしょうがないですね。
逆に言えば、リリースに向けて動いているからなのかもしれないですね。

はじめに

このツイートを見て、どういう風に認証を通しているのかが気になったので、実際に試してみた結果のメモをまとめた記事になります。

https://twitter.com/toricls/status/1438120050167189510

何をやっているのか理解したいので、そのまま試すのではなく、1つずつ順番に紐解いてやっています。
なお、調査の過程はこちらのスクラップに書いています。

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

調査した内容の概要

  1. GitHub Actions の環境変数の情報からIDトークンを取得する
  2. IDトークンからAWSの一時的な認証情報を取得できる設定をする
  3. 一時的な認証情報を取得しAWSリソースにアクセスする

GitHub Actions の環境変数の情報からIDトークンを取得する

まずは GitHub Actions 上でIDトークンを取得する方法を順番に試しました。

テスト用のワークフローファイル(YAML)

GitHub Actions 上にある環境変数を表示するために、以下の最小限のワークフローファイルを作成しました。

name: "test_github_oidc"

on:
push:
branches:
- "test-github-oidc"

jobs:
test_github_oidc:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Show env
run: env | grep 'ACTIONS_ID_TOKEN'

ポイントは permissions.id-token: write の部分です。
これを指定することでIDトークンの取得に必要な情報を環境変数に設定してもらえます。

この id-token という項目は、現時点では公式ドキュメントに書かれていません。
これが公式にアナウンスされれば実用的に使えるようになる、というのが現状かと思います。

https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions

image.png

GitHub Actions での実行結果

実行すると以下の2つの環境変数が出力されます。

image.png

2段構えになっているのは、全部の実行でIDトークンが必要になるわけではないので、無駄に発行しないためとかかなと想像しています。
(私は当初IDトークンそのものが環境変数に入っていると思っていたので、ちょっとハマりました)

IDトークンの取得

GitHub Actions 内で以下のように curl コマンドを実行するとIDトークンが取得できます。

$ curl --silent -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" "${ACTIONS_ID_TOKEN_REQUEST_URL}"
{
"count": 1390,
"value": "eyJ0e...AB3w"
}

value がIDトークンになります。(値はマスクしています)

IDトークンからAWSの一時的な認証情報を取得できる設定をする

IDトークンさえ取得できてしまえば、既存のAWSの機能を使ってAWSの一時的な認証情報を取得できます。
具体的な作業としては OpenID Connect のプロバイダを設定し、IAMロールを作成します。

AWSコンソールで設定したので、キャプチャをベースで簡単に説明します。

1. 新しいプロバイダの設定

image.png

image.png

これは「GitHub から対象のリポジトリに向けたIDトークンを信頼する」といった意味合いになります。
他のリポジトリ向けに発行されたIDトークンでは使うことができないので安心ですね。

2. ロールの作成

プロバイダの情報が選べるようになっているので、以下のように選択します。

image.png

3. 権限の設定

それぞれの要件に応じた最小限の権限を設定します。
今回は list-bucket でもやってみようかと思うので AmazonS3ReadOnlyAccess を選択してみました。

image.png

4. 作成

image.png

補足

こちらの記事の CloudFormation の定義の以下の部分に当たる作業です。

capture.png

これで、GitHub から発行されたIDトークンを使って、AWSにアクセスするための設定ができました。

一時的な認証情報を取得しAWSリソースにアクセスする

あとは実際にアクセスする処理を書いていきます。

一時的な認証情報を取得する方法

調べてみたところ AssumeRoleWithWebIdentity というAPIでIDトークンからAWSの一時的な認証情報を取得できるようです。

https://docs.aws.amazon.com/ja_jp/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html

AWS CLI のヘルプはこちらです。

https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html

あるいは CLI のヘルプでも見られます。

$ aws sts assume-role-with-web-identity help

(ちなみに STS のヘルプを見ている時に見つけました)

$ aws sts help

AWS CLI でAWSの一時的な認証情報を取得する

CLI を使って取得する場合は以下のようなコマンドで取得できます。
(※一部マスクしてます)

$ aws sts assume-role-with-web-identity \
--role-arn 'arn:aws:iam::000000000000:role/GitHub_OIDC_test' \
--role-session-name 'SESSION_NAME' \
--web-identity-token 'eyJ0...ds5BA'

{
"Credentials": {
"AccessKeyId": "AS...FP",
"SecretAccessKey": "Td...xD",
"SessionToken": "IQ...PA",
"Expiration": "2021-09-18T03:03:56Z"
},
"SubjectFromWebIdentityToken": "repo:mryhryki/*****:ref:refs/heads/test-github-oidc",
"AssumedRoleUser": {
"AssumedRoleId": "ARXXXXXXXXXXXXXXXXXTS:SESSION_NAME",
"Arn": "arn:aws:sts::000000000000:assumed-role/GitHub_OIDC_test/SESSION_NAME"
},
"Provider": "arn:aws:iam::000000000000:oidc-provider/vstoken.actions.githubusercontent.com",
"Audience": "https://github.com/mryhryki/*****"
}

GitHub Actions 上で実行する

最終的にこのようなワークフローを作って実行してみました。
(ほとんどシェルスクリプト・・・)

name: "test_github_oidc"

on:
push:
branches:
- "test-github-oidc"

jobs:
test_github_oidc:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- run: |
export AWS_DEFAULT_REGION="ap-northeast-1"
ID_TOKEN="$(curl --silent -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" "${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -r '.value')"
ACCESS_KEY_JSON="$(aws sts assume-role-with-web-identity --role-arn "arn:aws:iam::000000000000:role/GitHub_OIDC_test" --role-session-name "${GITHUB_RUN_ID}" --web-identity-token "${ID_TOKEN}" --output json)"
export AWS_ACCESS_KEY_ID="$(echo "${ACCESS_KEY_JSON}" | jq -r '.Credentials.AccessKeyId')"
export AWS_SECRET_ACCESS_KEY="$(echo "${ACCESS_KEY_JSON}" | jq -r '.Credentials.SecretAccessKey')"
export AWS_SESSION_TOKEN="$(echo "${ACCESS_KEY_JSON}" | jq -r '.Credentials.SessionToken')"
aws s3 ls

実行すると、無事S3バケットの一覧が取得できました!🎉

image.png

補足

こちらの記事では $AWS_WEB_IDENTITY_TOKEN_FILE のパスにIDトークンを入れているので何やってるんだろうと思ったんですが、IDトークンを入れたファイルのパスを $AWS_WEB_IDENTITY_TOKEN_FILE (と AWS_ROLE_ARN)に指定しておくと自動的に取得してくれる仕組みがCLIあるんですね。
全然知らなかった・・・。

It works because the AWS SDKs (and AWS CLI) support using the AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN environment variables since AWS EKS needed this.

EKSもこの機能を使っているのかぁ。

ドキュメントも見つけました。

https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#assume-role-with-web-identity

image.png

まとめ

GitHub Actions からセキュアにAWSにアクセスできる、本当に嬉しい機能で、早く公式にアナウンスされないかな〜、という気持ちになりました。
GitHub が発行したIDトークンで、AWSの一時的な認証情報を取得できるというシンプルさも好印象です。

参考リンク


※この記事は以下に投稿した記事のクロスポストです
https://zenn.dev/mryhryki/articles/2021-09-19-access-aws-by-github-actions