GitHub ActionsとGoogle CloudのOIDCの仕組みを理解する
GitHub Actions から AWS や GCP などのクラウドリソースを操作するときは、 OIDC を使用することが主流だと思いますが、手順に沿って設定はできるもの仕組みがよく分かっていない方も多いと思います。
この問題は厄介で、様々な"分からない"が絡まりあって生まれている問題だと思います。
例えば、
- どんな仕組み・流れでAWS・GCPを操作できるようになっているのか分からない(私)
- そもそもなぜOIDCを設定すると嬉しいのか分からない(私)
- 色々な設定をしたけど何をしているのか分からない(私)
などが挙げられると思います。
これらを解消し、OIDCを利用したGitHub ActionsとGCPの連携の流れ・仕組みを探求するのがこの記事の目的です。
※Google CloudのことはGCPと書きます。
※記事で触れないこと
GitHub Actions - Google Cloud間のOIDCによる認証の設定手順には触れません。こちらの記事で詳細な設定手順が書かれてありましたので、掲載させていただきます。
OIDC連携の仕組みを色々な観点から考えてみる
今回のトピックを色々な観点から考えて、疑問を解消していきたいと思います。
a. そもそも何を実現・解決したかった?
OIDCを利用するよう設定していますが、そもそもなぜこのような設定しているのでしょうか?実現したかったこと・解決したかったことをベースに考えてみます。
実現したかったことは、もちろん「GitHub Actionsからクラウドリソースを操作すること」です。そのためにやるべきことは簡単です。
- GCP: サービスアカウントキーを発行して使用すれば良い
- AWS: アクセスキーとシークレットキーを発行して使用すれば良い
ただし、これらのキーは有効期限が無期限であるため、漏洩時のリスクが高くなる問題があります。もっと安心してGitHub Actionsからクラウドリソースを操作したい…。そこでこれを可能にするのが Workload Identity連携 の利用です。
このWorkload Identity連携を使うと、OIDCの仕組みを利用して先ほどの無期限キーを発行することなく、GCPリソースにアクセスできるようになります。この、安全な方法でGitHub Actionsからクラウドリソースを操作することが実現したいことです。
※Workload Identity連携とは?
Workload Identity連携とは、文字通り外部ワークロードのIDをGCPと連携する機能です。つまり、外部のIDを使ってGCPを操作することを可能にします。上記のドキュメントにもありますが、外部のIDというのは、
- AWS
- OIDCをサポートするIdP
- SAML2.0
などで発行されたIDを指します。今回の場合は、「GitHubという外部ワークロードで発行されたID」を連携し、GCPを操作することを可能にする流れになります。GitHubはOIDCをサポートするIdPです。
b. どんな仕組み・流れでAWS・GCPを操作できるようになっている?
Workload Identity連携が使われていることは分かりましたが、実際にはどのような流れでGCPの操作を可能にしているのでしょうか。それを簡単にまとめたのがこの図です。
「2」以降はGCPの話です。流れとしては以下になります。
- GitHub OIDCトークンを発行する
- 発行したOIDCトークンをもとに
STS API
を叩き、STSトークンを入手する - STSトークンを使って
Service Account Credentials API
を叩き、サービスアカウントのIDトークンを入手する - 発行したサービスアカウントのIDトークンでリソースを操作する
これらの流れは、google-github-actions/auth アクション(GitHub ActionsからGCPを扱うときはだいたいこのアクションを使うはず。)の内部を読み進めていくことで分かります。それぞれを詳しく見ていきます。
1. GitHub OIDCトークンを発行する
まず、main()
関数内の121行目の箇所で getIDToken()関数が呼ばれていることが分かります。
この関数は @actions/core
で定義されています。中を見ると、ACTIONS_ID_TOKEN_REQUEST_TOKENをBearerトークンとしてACTIONS_ID_TOKEN_REQUEST_URL にHTTPリクエストを投げていることが分かります。
この2つの環境変数の意味はこちらのドキュメントで定義されていますが、内容は
- GitHub OIDCプロバイダーのURL
- GitHub OIDCプロバイダーを利用するためのトークン
です。このプロバイダーにアクセスし、OIDCトークン(正体はJWT)を取得しています。もちろんこのトークンはshort-livedで安全です。
ところで、OIDCトークンを取得しているのはわかったのですが、このトークンは結局何者なのか、何のためにあるのか、を再度確認しておきます。
こちらのドキュメントの3章を少しだけ見ると、この発行されたトークンはAuthentication結果を含んでいるということが分かります。つまり、OIDCトークンは何かを認証したものです。今回の場合は何を認証したのかと言うと、この今まさに実行中のGitHub Actionsワークフローは確かに存在することを証明(認証)したことになります。
2. クレデンシャルファイルの生成
続いて、main()関数の183行目でクレデンシャルファイルを生成しているのが分かります。実装はこちらで、色々書かれたjsonのファイルを作成しています。
ドキュメントを見ると、このクレデンシャルファイルは「認証情報構成ファイル」と呼ばれています。
ドキュメントにあるように、この認証情報構成ファイルがあればgcloud
やterraform
などのツールがそのファイルの設定を読み、自動的にサービスアカウントの権限を借用するようになるようです。以下のような情報が入っていることが分かります。
- 外部認証情報の取得元
- 使用する Workload Identity プールとプロバイダ
- 権限を借用するサービス アカウント
3. 発行したOIDCトークンをもとにSTS APIを叩き、STSトークンを取得する
認証情報構成ファイルを作成した後はswitch文に入るのですが、google-github-actions/auth
をデフォルトの設定で使う場合はtokenFormat
は空なので、何も起きず全体の処理はここで終了します。
switch (tokenFormat) {
case '': {
break;
}
つまり、意外にもOIDCトークンを発行して認証情報構成ファイルを生成するまでがアクションの仕事内容であり、GCPと実際にやりとりしてサービスアカウントの権限を借用する流れはアクションの中で行いません(gcloud
やterraform
などの後のツールが認証情報構成ファイルを元にその流れを行います)。
とはいえ、そちらのツールの実装を見に行くのは骨が折れそうなのと、google-github-actions/auth
の中でもtokenFormat
がid_token
の場合のswitch分岐の中にサービスアカウントの借用まで行うコードは書かれてあるので、このままコードを参考に解説していきます。
続きとして274行目を見ると、AuthTokenを取得していることが分かります。この関数の本体はこちらで、中を見るとどうやらhttps://sts.googleapis.com/v1/token
にリクエストを投げているようです。
ドキュメントによると、それは Google Security Token Service のAPIであり、Exchanges a credential for a Google OAuth 2.0 access token
と書かれていることから、先ほど発行したOIDCトークンをGoogle OAuth 2.0アクセストークンに交換していることが分かります。
交換するときにはGitHubのOIDCトークンの検証も行っています。検証の方法についてはここでは触れませんが、GCPは「もらったOIDCトークン(GitHubのIdP発行)が確かに本物だと検証すること」ができていて、安心してアクセストークン(=STSトークン)を渡しています。
検証方法はこちらの記事が大変参考になったので、掲載させていただきます。
とにかくこれでGCPを操作できそうなトークンを取得することができました。しかし、実はまだリソースを操作するための十分な権限を持っていません。トークン発行時にscope
をhttps://www.googleapis.com/auth/cloud-platform
としているので、このスコープ範囲の操作までは境界として許可されているので、割となんでもできそうな気はしますが、実際のリソースへのアクセス許可はIAMポリシーで定義されるためです。
このWorkload Identityで管理されているトークンにアタッチされているIAMポリシーといえば、OIDCフローの設定時に付与したroles/iam.workloadIdentityUser
ロールの権限ですが、権限の内容を確認してみてもGitHub Actionsからやりたいことであるterraform plan
でほとんどのリソースを参照することや、CloudRun
に何かをデプロイすることはできなさそうだと分かります。
※https://www.googleapis.com/auth/cloud-platform
スコープができることはおそらくこのドキュメント。
Service Account Credentials API
を叩き、サービスアカウントのIDトークンを入手する
4. STSトークンを使ってコードを読むと、続いてgoogleIDToken()という関数を呼び出していることがわかります。この関数の定義はこちらで、中を読むと先ほど発行したSTSのauthToken
を使ってhttps://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:generateIdToken
というエンドポイントにPOSTを投げていることが分かります。
ドキュメントにはGenerates an OpenID Connect ID token for a service account
と書かれてあることから、このAPIを使ってサービスアカウントのトークンを発行していることが分かります。なおこれもshort-livedです。
今回こそ対象のサービスアカウントのトークンを取得し、いわゆる権限借用を行うことができました。(ちなみに、このAPIを使ってサービスアカウントのトークンを取得することはroles/iam.workloadIdentityUser
でちゃんと許可されています。)
これでようやくリソースの操作が行えるようになったわけです。
5. 発行したサービスアカウントのIDトークンでリソースを操作する
…特に説明はないです。
これでGitHub ActionsがOIDCを利用してGCPのサービスアカウントの権限を取得する大体の流れは押さえることができたと思います。
c. 色々な設定をしたけど何をしているのか
これまでの流れを押さえた上で、GitHub ActionsやGCP側に行った色々な設定の中身を見ていきます。
1. Workload Identityプールの作成
GCP側でWorkload Identityプールを作成しました。これは、各種プロバイダから発行される外部IDを管理する場所です。(※GitHubのIDトークンならhttps://token.actions.githubusercontent.com
で発行される)
そのため、プールの作成と同時にプールにプロバイダ設定を追加する必要があったと思います。これにより、今回作成したWorkload Identityプールで管理するIDのプロバイダを指定・制限していたのです。
2. 属性マッピング
google.subject = assertion.sub
などの属性マッピングの設定をしたと思います。これは、GitHubのIdPから発行されるトークンをSTS APIで交換するときに、STSトークンの形と合わない部分や足りない部分があるため、設定が必要だったのです。
以下のドキュメントを見ると、google.subject
がSTSトークンでは必須なことが分かります。残りのマッピングしたものはドキュメントは見つけられませんでしたが、必要な作業だったのだと思います。
3. サービスアカウントの権限設定
サービスアカウントに対してリソースへの操作権限を持ったIAM Roleをアタッチするのはもちろんですが、その他にもWorkload Identity PoolのIAMプリンシパルに対して、roles/iam.workloadIdentityUser
権限を付与し、サービスアカウントに対してのアクセス権限を付与したと思います。
これは、Workload Identityで管理されている外部IDに対してサービスアカウントのトークンを発行する権限を与える設定です。
まとめ
GitHub ActionsとGoogle Cloud間のOIDCを利用した連携の仕組みについて、コードを読みつつ解明していきました。
どんな仕組み・流れでAWS・GCPを操作できるようになっているのか分からない
→ 以下のような流れで操作できるようになっていたことが分かりました。
- GitHub OIDCトークンを発行する
- 発行したOIDCトークンをもとに
STS API
を叩き、STSトークンを入手する - STSトークンを使って
Service Account Credentials API
を叩き、サービスアカウントのIDトークンを入手する - 発行したサービスアカウントのIDトークンでリソースを操作する
ただし、google-github-actions/auth
アクションがデフォルトで行っているのは「GitHub OIDCトークンの発行」と「認証情報構成ファイルの生成」まで、STS APIとのやり取りはその後の各種ツールが行っています。
そもそもなぜOIDCを設定すると嬉しいのか分からない
→ 主には、発行するトークンがshort-livedになったり検証が加わったりと、漏洩リスクが減ってセキュリティが向上することがざっくり分かりました。
今回の記事がOIDCの流れを理解する助けとなっていれば幸いです。
Discussion