みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

Workload Identity で Github Actions を快適に認証する

Google Cloud の Workload Identity を使用して,Github Actions から認証できるように設定します.

Workload Identity とは

Workload Identity allows your workloads to access Google Cloud without Service Account keys. There are 4 steps to setting up a workload identity.

  1. Create a workload identity pool: The pool organizes and manages external identities. IAM lets you grant access to identities in the pool.
  2. Connect an identity provider: Add either AWS or OpenID Connect (OIDC) providers to your pool.
  3. Configure provider mapping: Set attributes and claims from providers to show up in IAM.
  4. Grant access: Use a service account to allow pool identities to access resources in Google Cloud.

Workload Identity により,サービスアカウントの鍵を使わずに,作業プログラムが Google Cloud にアクセスできるようになります.以下の4段階で workload identity を設定します.

  1. workload identity pool を作成します.これは外部のアイデンティティを整理し管理します.IAM で pool のアイデンティティにアクセスを許可しましょう.
  2. identity provider に接続します.具体的には,pool に AWS か OpenID Connect (OIDC) provider を追加します.
  3. provider mapping を設定し,providerからの属性や要求が IAM に表示されるようにします.
  4. アクセスを許可し,サービスアカウントを使用して,pool に含まれるアイデンティティで Google Cloud のリソースにアクセスします.

https://console.cloud.google.com/iam-admin/workload-identity-pools (拙訳 みー)

Workload Identity を設定する

プロジェクトを開いて,"IAM & Admin" > "Workload Identity Federation" を開き,pool を作成します (step 1).名前は sample (任意)とします.

provider は OIDC (固定) ,名前は my-provider (任意),issuer は "https://token.actions.githubusercontent.com" (固定) ,最初なのでdefault audience に設定します (step 2).

provider attributes は最低限 subject のみ設定すればよいです (step 3).

作成が完了しました.IAM principal が,principalSet://iam.googleapis.com/projects/5xxxxx12/locations/global/workloadIdentityPools/sample/* となります.

次にサービスアカウントにアクセスを許可します (step 4).別の画面で logger という名前のサービスアカウントを作成し,logs writer を付与しておきましたので,このサービスアカウントを使用します.

接続が完了しました.

Google Cloud で初回に必要な設定は以上です.

サービスアカウントとして認証する

Github Actions を以下のように設定します.

.github/workflows/oidc.yml

name: CI

on: [push]

permissions:
  contents: read
  id-token: write

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - id: auth
        uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: "projects/5xxxxx12/locations/global/workloadIdentityPools/sample/providers/my-provider"
          service_account: "logger@xxxxxxxx.iam.gserviceaccount.com"
      - uses: google-github-actions/setup-gcloud@v1
      - run: |
          gcloud logging write github-actions '{"message": "success"}' --payload-type=json

実行すると以下のようにログが出力されました.

一見すると,.github/workflows/oidc.yml には一切機密情報が含まれていないのに認証ができており,素晴らしいように見えますが,workload_identity_providerservice_account の値が漏洩すると,別の Github アカウントの Github Actions でも認証できてしまうので問題です.

監査ログを使用する

IAM can generate audit logs when principals exchange a token. To receive audit logs for all steps of the token-exchange process, you must enable audit logs for Data Access activity for the following APIs:

  • Identity and Access Management (IAM) API (enable log type "Admin Read")
  • Security Token Service API (enable log type "Admin Read")

After you enable audit logs for Data Access activity, IAM generates an audit log entry each time a principal exchanges a token. The log entry includes the following fields:

  • protoPayload.authenticationInfo.principalSubject: The subject of the identity provider token.

For other OIDC identity providers, this field contains the value of the sub, or subject, claim from the OIDC token.

https://cloud.google.com/iam/docs/audit-logging/examples-workload-identity

上記に従って以下のように設定します.

https://console.cloud.google.com/iam-admin/audit

先程の Github Actions を再実行したところ,以下のような監査ログが生成されました.

{
  "protoPayload": {
    "@type": "type.googleapis.com/google.cloud.audit.AuditLog",
    "status": {},
    "authenticationInfo": {
      "principalSubject": "repo:xxxxxx/xxxxxxx:ref:refs/heads/master"
    },
  "severity": "INFO",
  "logName": "projects/xxxxxxxx/logs/cloudaudit.googleapis.com%2Fdata_access",
  ...
}

principalSubject の値が,GitHub のヘルプに記載されている,"repo::ref:refs/heads/branchName" に一致していることが確認できました.

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#filtering-for-a-specific-branch

この設定により,誰かが Github Actions 上で勝手に認証を得ようとしたら,その情報がログに残ります.

許可する範囲を制限する

現在の設定では,workload_identity_providerservice_account の値を知っている Github Actions 上のジョブならば,認証が成功してしまいます.相手である Google Cloud で制限が必要です.Github Actions は以下のようなフィールドを Google Cloud に提供するので,この値が適切な場合のみ許可をするべきです.

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token

例えば,repository が特定の値である場合のみ許可するならば以下のように設定します.

全く同じ Github Actions スクリプトを2個のレポジトリに作成し,片方のレポジトリのみ許可する設定にして実行したところ,以下のように許可したレポジトリでは成功し,もう片方のレポジトリではエラーとなりました.

これで,実行するレポジトリを適切に制限できました.

Terraform で管理する

例えば以下のような構成が可能です.

provider "google" {
  project = var.project_id
}

variable "project_id" {
  type = string
}

variable "repository" {
  type        = string
}

resource "google_iam_workload_identity_pool" "main" {
  project                   = var.project_id
  workload_identity_pool_id = "main"
  display_name              = "Main Pool"
  description               = "Main Pool"
  disabled                  = false
}

resource "google_iam_workload_identity_pool_provider" "main" {
  project                            = var.project_id
  workload_identity_pool_id          = google_iam_workload_identity_pool.main.workload_identity_pool_id
  workload_identity_pool_provider_id = "main"
  display_name                       = "Main Provider"
  description                        = "Main Provider"
  attribute_condition                = "assertion.repository==\"${var.repository}\""
  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
  }
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

resource "google_service_account" "logger" {
  account_id   = "logger"
  display_name = "A service account for logging"
}

resource "google_project_iam_binding" "oidc" {
  project = var.project_id
  role    = "roles/logging.logWriter"
  members = [
    "serviceAccount:${google_service_account.logger.email}"
  ]
}

resource "google_service_account_iam_binding" "oidc" {
  service_account_id = google_service_account.logger.name
  role               = "roles/iam.workloadIdentityUser"
  members = [
    "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.main.name}/attribute.repository/${var.repository}"
  ]
}

参考文献