🐳

【きちんと理解する】Workload Identity FederationをGitHub Actionsで利用する

2022/04/14に公開

概要

Workload Identity Federation(以下、WIF)は、GCP が利用する認証機構の一種で、これを使うことにより、外部システムのユーザーが GCP 内のユーザーを借用できるようになります。簡単に内容を説明しましょう。

今回は、GitHub Actions と GCP の連携例に考えます。GitHub では、WIF に対応する ID プロバイダを用意しており、このプロパイダが認証サーバの役割を果たしています。GCP では、GitHub が用意した ID プロパイダが提供する認証情報に応じて、ユーザー権限を与えることができます。GitHub Actions は GitHub が用意したプロパイダで認証することにより、GCP のリソースに変更を加えることができます。

結果として、「GCP 以外のサービスが提供する外部 ID を用いて、GCP リソースに変更を加える」ことができます。

利点

この仕組みができる前は、サービスアカウントキーを利用する方法が主流でした。しかし、サービスアカウントキーを利用する方法は、該当のサービスアカウントキーが漏洩してしまうと、誰でも GCP リソースに変更を加えることができてしまいます。今回の仕組みを利用することで、そもそも長期間有効な認証情報をエクスポートする必要がなくなるため、認証情報が漏れることによるリスクを負わずに済むようになります。

事前準備

前提として何が必要か、Google Cloud の公式ドキュメントの、プロジェクトを準備するの項を確認しておいてください。実行できていないステップがあると、エラーが発生します。

「GCP 以外のサービスが提供する外部 ID を用いて、GCP リソースに変更を加える」ことを実現するためには、まず事前に外部 ID に対して信頼する設定を行う必要があります。そのためには、GCP 側で以下のリソースを作成する必要があります。

  1. Workload Identity プール
  2. OIDC ID プロパイダ

Workload Identity プールには、複数の OIDC ID プロパイダが所属することが可能で、プールに対してサービスアカウントを借用する許可を与えることになります。OIDC ID プロパイダは、その名の通り、ID プロパイダに関する接続情報などをまとめたリソースです。今回であれば、GitHub が提供している ID プロパイダを指すことになります。

これら 2 つのリソースを作成することによって、Workload Identity 連携が可能になります。

次に、権限の借用許可を与える条件として「特定のリポジトリへのプッシュ時のみサービスアカウントを渡したい」という条件を考えます。この条件を満たしている際にのみサービスアカウントを借用できるようにするために、GitHub の ID プロパイダがどのような応答をするのか、確認してみましょう。

GitHub の ID プロパイダについて

GitHub が用意した ID プロパイダには2つの役目があります。

  1. 認証を行う
  2. 認証情報(Open ID Connect トークン、以下 OIDC トークンと呼称)を発行する

1 については今回のメイントピックではないため、省略します。GitHub のドキュメントには特に書かれておらず、基本的には GitHub のユーザーなら誰でも OIDC トークンを取得できるためです。

次に、OIDC トークンを発行する点ですが、これは GCP に対して発行される情報です。この情報をもとに、信用するかどうかを判定するわけですね。

具体的に、OIDC トークンの内容に触れてみましょう。以下は OIDC トークンのサンプルです。[1]1つ目のブロックはヘッダー情報、2 つ目のブロックはペイロード情報が入っています。本来であれば、3 つ目のブロックに内容を検証するための署名が入っていますが、今回のサンプルでは省略されています。

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "example-thumbprint",
  "kid": "example-key-id"
}
{
  "jti": "example-id",
  "sub": "repo:octo-org/octo-repo:environment:prod",
  "environment": "prod",
  "aud": "https://github.com/octo-org",
  "ref": "refs/heads/main",
  "sha": "example-sha",
  "repository": "octo-org/octo-repo",
  "repository_owner": "octo-org",
  "run_id": "example-run-id",
  "run_number": "10",
  "run_attempt": "2",
  "actor": "octocat",
  "workflow": "example-workflow",
  "head_ref": "",
  "base_ref": "",
  "event_name": "workflow_dispatch",
  "ref_type": "branch",
  "job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main",
  "iss": "https://token.actions.githubusercontent.com",
  "nbf": 1632492967,
  "exp": 1632493867,
  "iat": 1632493567
}

ヘッダー情報には、OIDC での基本情報、署名の検証アルゴリズムなどが記載されています。この情報によって、受け取った OIDC トークンをどのように処理すべきか GCP 側に伝えるわけですね。

ペイロード情報には、GitHub 上で、誰がどのリポジトリで、どのワークフローを起動したかなどの情報が記載されています。ペイロード情報のうち、特筆すべき項目はsubで、これは OIDC の中で取り決められている一意の識別子です。OIDC の取り決めではsub以外はカスタマイズ可能であるため、sub以外の情報だけで権限を付与すると、他のリポジトリから認証されてしまう恐れがあります。

subに入る内容については、ワークフローをトリガーするイベントごとに異なるようで、具体的な値については参考資料[2]を確認してください。

今回の例を見ると、subの中に、リポジトリ名と、ブランチ名、audの中にユーザー名が書かれているようです。また、issに GitHub の認証プロパイダの URL が入っていることがわかります。これらの情報をもとに GCP 側で信頼設定を行いましょう。

GCP 側の設定 プールとプロバイダの設定

まずは、GCP で ID プールを作成します。「IAM と管理」> 「Workload Identity 連携」を開き、「使ってみる」を選択すると、新しいプールを作成する画面に移行します。

ID プールを作成するためには、名前が必要なので、適当に決めて入力を行います。ID も必要ですが、自動的に生成されるため、もし変更したい場合は編集してください。

次に、ID プールに対して、GitHub の ID プロパイダを登録します。発行元にhttps://token.actions.githubusercontent.comを指定し、許可するオーディエンスとして、自分自身のプロフィールの URL を設定します。(後述の GitHub Actions での設定と一致するようにします)

それから、発行された OIDC トークンから、判定に利用する属性をマッピングします。 今回は、repositoryの属性に入っている値を使いたいこと、google.subjectsubの情報を渡す必要があるため、以下のようにマッピングします。

最後に、マッピングした属性をもとに、条件を定めます。今回は特定のリポジトリからのプッシュのみ受け付けたいため、以下のように設定します。

'(ユーザー名)/(リポジトリ名)' == attribute.repository

保存を押して、ID プールを作成しましょう。

GCP 側の設定 借用するサービスアカウントの設定

次に、ID プールに対して、借用するサービスアカウントを設定します。サービスアカウントの作成方法については、この記事では割愛します。
万が一悪用された場合に備えて、必要最低限の権限だけを付与したサービスアカウントを作成してください。

作成が完了したら、先程作成した ID プールの詳細画面を開きます。

ID プールの詳細画面から、アクセスを許可のボタンを押しましょう。

先程作成したサービスアカウントを追加して、保存すれば完了です。
(セキュリティ上問題になるため、スクリーンショットはありません。)

実際に使ってみる

GCP 側の設定は終わったので、あとは GitHub Actions で以下のアクションを設定するだけです。
なお、このアクションを実行する前に、必ずactions/checkout@v3[3]を実行する必要があります。

    - name: 'Authenticate to Google Cloud'
      uses: 'google-github-actions/auth@v0'
      with:
        workload_identity_provider: 'projects/(GCPのプロジェクトナンバー)/locations/global/workloadIdentityPools/(IDプールのID)/providers/(プロパイダのID)'
        service_account: '(サービスアカウント名)@(GCPのプロジェクトID).iam.gserviceaccount.com'
        audience: 'https://github.com/(ユーザ名)'

まとめと感想

今回は Workload Identity Federation について説明しました。
サービスアカウントキーを利用する方法など、さっと認証できる手段は大変楽ではあるものの、シークレットが漏洩したときの問題と隣合わせであるため、可能な限りセーフな仕組みを利用すれば後々セキュリティに関して後々頭を悩ませずに済みます。
特に運用に関わる変更コストを組織が受け入れられなくなってしまうと、後は地獄になってしまうので、早いうちに対処することが望ましいでしょう。

初めて私がこの仕組みを利用しようと考えたとき、パッと見で理解できる資料が見つからなかったため、こうやってブログにまとめていますが、久々の執筆はかなり疲れますね。また、気が向いたときに定期的に書くことにします。

参考文献

脚注
  1. GitHub Docs About security hardening with OpenID Connect #Understanding the OIDC token (https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token) ↩︎

  2. GitHub Actions の OpenID Connect サポートについて(https://zenn.dev/miyajan/articles/github-actions-support-openid-connect#github-actions-の-oidc-トークンの-sub-にはなにが入るのか?) ↩︎

  3. google-github-actions/auth(https://github.com/google-github-actions/auth) ↩︎

Discussion