Workload Identity FederationをGitHub Actionsで利用する

11920 ワード

概要

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) ↩︎