🔒

Github Actions から OIDC で Google Container Registry にイメージをプッシュする

2023/09/19に公開

この記事は株式会社Gincoのテックブログとして書いています。

はじめに

  • 業務で掲題のワークフローを実装する必要があったのでまとめました
  • OIDC を利用することで Github にクレデンシャルを渡す必要がなくなり、よりセキュアな運用が可能になります
  • コンソールから行う OIDC の設定手順と実装したワークフローが書いてあります

OIDC の設定手順

1. Workload Identity のプールとプロバイダを作成

  • IAMと管理 > Workload Identity 連携 をクリック

  • プールを作成 にアクセス。任意の名前を入力し、続行 をクリック

  • OpenID Connect(OIDC) を選択

  • 任意のプロバイダ名を入力

  • 発行元(URL)には https://token.actions.githubusercontent.com を入力

  • デフォルトのオーディエンスを選択

  • 以下のように属性をマッピングし、保存をクリック

    • google.subject => assertion.sub
    • attribute.repository => assertion.repository
    • attribute.actor => assertion.actor

2. Github Actions から利用するサービスアカウントの作成

  • IAMと管理 > サービスアカウント をクリック

  • サービスアカウントを作成をクリック

  • 任意のサービスアカウント名を入力し、作成して続行をクリック

  • サービスアカウントにストレージ管理者のロールを付与し、完了 をクリック

3. Github Actions にサービスアカウントの権限借用を許可する

  • IAMと管理 > サービスアカウント をクリック

  • 作成したサービスアカウントをクリック

  • 権限をクリック

  • アクセス権を付与をクリック

  • 以下のフォーマットで新しいプリンシパルを入力、ロールWorkload Identity ユーザーを選択

    principalSet://iam.googleapis.com/projects/{GCPプロジェクトのID}/locations/global/workloadIdentityPools/{作成したプールのID}/attribute.repository/{Githubのユーザー名}/{リポジトリ名}
    

  • プールの詳細画面にて接続済サービスアカウントに作成したサービスアカウントが表示されていれば OK
    • 反映まで数秒時差があります
    • 表示されない場合、新しいプリンシパルの値が間違っている可能性があります

Github Actions の実装

最終的なワークフロー

.github/workflows/build-push-prd.yml
name: Call build-push workflow for prd

on:
  push:
    tags:
      - v**

jobs:
  call_build_push_workflow:
    if: ${{ github.ref_name == 'main' }}
    name: Call workflow
    uses: ./.github/workflows/reusable-build-push.yml
    with:
      build_context: path/to/build_context
      file: path/to/Dockerfile
      gcr_host: asia.gcr.io
      gcr_repository_host: asia.gcr.io/your-repository/your-image
    secrets:
      workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER_FOR_PRD }}
      service_account_address: ${{ secrets.SERVICE_ACCOUNT_ADDRESS_FOR_PRD }}
.github/workflows/reusable-build-push.yml
name: Reusable build-push workflow 

on:
  workflow_call:
    inputs:
      build_context:
        type: string
        required: true
      build_target:
        type: string
        required: true
      file:
        type: string
        required: true
      push:
        type: boolean
        default: true
        required: false
      gcr_host:
        type: string
        required: true
      gcr_repository_host:
        type: string
        required: true
    secrets:
      workload_identity_provider:
        required: true
      service_account_address:
        required: true

jobs:
  build_push:
    name: build-push image
    runs-on: ubuntu-latest
    permissions:
      contents: 'read'
      id-token: 'write'
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Build-Push image
        uses: ./.github/actions/build-push
        with:
          workload_identity_provider: ${{ secrets.workload_identity_provider }}
          service_account_address: ${{ secrets.service_account_address }}
          gcr_host: ${{ inputs.gcr_host }}
          gcr_repository_host: ${{ inputs.gcr_repository_host }}
          build_context: ${{ inputs.build_context }}
          build_target: ${{ inputs.build_target }}
          file: ${{ inputs.file }}
.github/actions/build-push/action.yml
name: Build-Push
description: Build and push image or only build image
inputs:
  workload_identity_provider:
    description: Principal of identity provider for OIDC
    required: true
  service_account_address:
    description: Service account address for GCP 
    required: true
  gcr_host:
    description: Host of GCR
    required: true
  gcr_repository_host:
    description: Repository host of GCR
    required: true
  build_context:
    description: Build context
    required: false
    default: .
  build_target:
    description: Target to build
    required: true
  file:
    description: File path to Dockerfile
    required: true
  push:
    description: Whether push image or not(= only build)
    required: false
    default: 'true'

runs:
  using: composite
  steps:
    - name: Set up buildx
      uses: docker/setup-buildx-action@v2

    - name: Authenticate to Google Cloud
      id: auth
      uses: 'google-github-actions/auth@v1'
      with:
        token_format: access_token
        workload_identity_provider: ${{ inputs.workload_identity_provider }}
        service_account: ${{ inputs.service_account_address }}

    - name: Login to GCR
      uses: docker/login-action@v2
      with:
        registry: ${{ inputs.gcr_host }}
        username: oauth2accesstoken
        password: ${{ steps.auth.outputs.access_token }}

    - name: Set docker metadata
      id: metadata
      uses: docker/metadata-action@v4
      with:
        images: ${{ inputs.gcr_repository_host}}
        tags: |
          type=semver,pattern={{raw}}
          type=sha,format=short

    - name: Build-Push image
      uses: docker/build-push-action@v4
      with:
        context: ${{ inputs.build_context }}
        target: ${{ inputs.build_target }}
        file: ${{ inputs.file }}
        push: ${{ inputs.push }}
        tags: ${{ steps.metadata.outputs.tags }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
        provenance: false

ポイントと解説

  • main ブランチに v で始まるタグが push されるとワークフローが実行される
  • OIDC 利用には permissionsid-token: write が必要
  • 下記の最終的なワークフローは本番環境用だが reusable workflowcomposite action を利用することで他環境用にも再利用可能
  • docker/setup-buildx-action@v2docker/build-push-action@v4 においてキャッシュを使えるようにする
  • OIDC トークンの発行は google-github-actions/auth@v1 で行われている
    • OIDC の設定手順で作成した workload identity provider のプリンシパルを指定
      • projects/プロジェクトID/locations/global/workloadIdentityPools/作成したpoolのID/providers/作成したプロバイダ名
    • OIDC の設定手順で作成した service account のアドレスを指定
      • github-actions@test-example-123456.iam.gserviceaccount.com
  • docker/login-action@v2 でOIDC トークンを使って GCR へ認証
  • docker/metadata-action@v4 でイメージのタグを出力
    • type=semver,pattern={{raw}} で 例えば Github 上で v1.2 というタグがプッシュされた時に同じタグを出力する
    • type=sha,format=short で git の short sha をタグとして出力する
  • docker/build-push-action@v4 でイメージのビルドとプッシュ
    • 2023年9月時点で Experimental となっているtype=gha として Github Action のキャッシュを利用
    • mode=max として全ての中間レイヤーもキャッシュさせる。デフォルトは min であり最終イメージのレイヤーしかキャッシュされない
    • provenance: false こちらの問題を防ぐために指定が必要でした

所感

  • CI からクラウドへの認証は OIDC の利用を当たり前としていきたい
  • コンソールから都度設定するのは手間なので、社内で統一していくにはこういった Terraform module を利用 or 作成したい

注意事項

  • Google Container Registry は 2024年5月15日以降にサポートが終了します
  • 特別な事情がない限りは Artifact Registry を代わりに使用してください
  • 最終的なワークフロー にある docker/login-action@v3registry に渡す値を任意の Artifact Registryのホストに変えればそのまま転用できると思います

最後に

  • 株式会社 Gincoではブロックチェーンを学びたい方、ウォレットについて詳しくなりたい方を募集していますので下記リンクから是非ご応募ください。
  • 株式会社Ginco の求人一覧

参考

https://cloud.google.com/blog/ja/products/identity-security/enabling-keyless-authentication-from-github-actions
https://cloud.google.com/iam/docs/configuring-workload-identity-federation?hl=ja#github-actions
https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers?hl=ja
https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform

GitHubで編集を提案

Discussion