Lento con forza

大学生気分のIT系エンジニアが色々書いてく何か。ブログ名決めました。

OpenID Connect連携を使ってGitHub ActionsからFirebase App Distributionでアプリを配布する

以前GitHub Actionsから、Firbease App Distributionにアプリを配布するエントリを書きました。

kouki.hatenadiary.com

エントリを書いた時のFirebase App Distribution認証では、ユーザーのトークンを使っていました。時代は移り、サービスアカウントや、OpenID Connectを使った認証を使えるようになっています。今回は、GitHub ActionsのOpenID Connect対応と Google CloudのWorkload Identityを使ってFirebaseの認証を行う方法を説明します。この方法では、Secretにユーザーのアクセストークンやサービスアカウントのキーファイルを保存する必要がなくなり、よりセキュアに認証を行えます。

GitHub ActionsのOpenID Connect対応

詳しくは別の記事を書いたので、詳しく知りたい方はそちらをみてください。簡単に説明すると、GitHub ActionsがジョブごとにOpenID Connectに準拠した認証情報を発行してくれます。

kouki.hatenadiary.com

GitHub ActionsでのOpenID Connect対応の詳細についてはこちらのドキュメントを参考にしてください。

docs.github.com

Google CloudのWorkload Identityで一時的なアクセストークンを発行

Workload Identityは、外部の認証情報を元にGoogle Cloud Platformへのアクセストークンを発行する仕組みです。

cloud.google.com

外部の認証情報の1つとして、OpenID Connectをサポートする任意のIDプロバイダを利用できます。任意のIDプロバイダとして、GitHub Actionsを使うことで、GitHub Actionsが発行したIDトークンを、Google Cloud上のアクセストークンに変換できます。

ドキュメントを参考に、Workload Identityを使う準備をしていきます。

cloud.google.com

上記のドキュメントの通り、APIを有効にした状態から始めます。

Workload Identity Poolを作成する

https://console.cloud.google.com/iam-admin/workload-identity-pools/createから、IDプールを作成します。

まずは、名前とIDを指定します。Firebase App Distributionのためのプールなので、ここでは firebase-app-distribution としました。

次に、プロバイダを追加します。プロバイダにはOpenID Connectを指定します。OpenID ConnectにおけるIdentity Providerを登録します。今回Identity Providerに当たるものは、GitHub Actionsなので、そのように指定していきます

名前
プロバイダ名 GitHub Actions
プロバイダID github-actions
発行元 https://token.actions.githubusercontent.com

発行元の情報は、ID Tokenのissの値です。GitHubのドキュメントから参照できます。

最後に、プロバイダの属性を構成します。プロバイダの属性を元に、不正なリクエストを防ぐことができます。IDトークンの値をattributeにマッピングして、属性条件で使うことができます。IDトークンに含まれる情報はGitHubのドキュメントに書いてあり、リポジトリに関する情報や、ジョブを発行したユーザーなどが使えます。この値を元に、アクセストークンの発行可否を決めるため、ユースケースごとに慎重に設計を行ってください。属性条件を設定しないと、悪意のあるユーザーが不正にアクセストークンを取得して悪用されてしまう可能性があります。

今回は、特定のリポジトリでのみ動作すれば良いので、repositoryの完全一致を確認します。追加情報として、僕以外の人が使えないようにするために、actorの一致も確認しています。

名前
google.subject assertion.sub
attribute.actor assertion.actor
attribute.repository assertion.repository

属性条件

attribute.repository == "kouki-dan/ActionsAndAppDistributionTest" && attribute.actor == "kouki-dan"

これで、Workload Identity Poolが作成できました。

Workload Identityにサービスアカウントを紐付ける

Workload Identityがアクセストークンを発行するために、サービスアカウントを紐づける必要があります。以下のドキュメントに沿って作業します。

cloud.google.com

https://console.cloud.google.com/iam-admin/workload-identity-pools から、先ほど作ったWorkload Identity プールを選択します。

プールの詳細から、「アクセスを許可」を押し、必要な権限を付与したサービスアカウントを指定します。もしサービスアカウントを作っていない場合は、作成してください。今回はFirebase App Distributionの配布のみに使うため、 Firebase App Distribution Admin SDK サービス エージェント のロールを割り当てました。

これで、Workload Identityを使う準備ができました。

後ほど、ここで作ったプロバイダーを特定するための情報を使うので、準備しておきます。以下の2つの情報を把握しておきます。

キー
workload-identity-provider projects/<project-id>/locations/global/workloadIdentityPools/<name-of-pool>/providers/<name-of-provider>
service-account サービスアカウントのメールアドレス

workload-identity-providerの<project-id>は、アルファベットのものではなく、数字のものを指定します、プールの詳細に情報が書いてあります。今回僕が作成したものは以下のようになります。

キー
workload-identity-provider projects/595091170546/locations/global/workloadIdentityPools/firebase-app-distribution/providers/github-actions
service-account firebase-app-distribution@actionsandappdistributiontest.iam.gserviceaccount.com

GitHub Actionsのワークフローを設定する

GitHub ActionsのIDトークンを使ってFirebaseの認証情報を取得する準備ができたので、Actionsの設定をしていきます。

IDトークンを取得するためにパーミッションを指定する

デフォルトの設定では、IDトークンが発行されないため、permissionsを指定する必要があります。トップレベルでも、ジョブレベルでも指定できます。

# main.yml
permissions:
  contents: read
  id-token: write

google-github-actions/auth を使ってアクセストークンを取得

認証情報を取得するためのActionが準備されているので、これを使います*1

https://github.com/google-github-actions/auth

先ほど準備した、<workload-identity-provider>と、<service-account>を、それぞれの引数に渡してください。

jobs:
  build:
    # 省略
    - id: auth
      uses: 'google-github-actions/auth@v1.0.0'
      with:
          workload_identity_provider: projects/595091170546/locations/global/workloadIdentityPools/firebase-app-distribution/providers/github-actions
          service_account: firebase-app-distribution@actionsandappdistributiontest.iam.gserviceaccount.com
          token_format: access_token

このActionでアクセストークンが発行され、後続のステップで利用できます。

アクセストークンを使ってFirebase App Distributionでデプロイする

アクセストークンが発行できたので、Firebase App Distributionにデプロイしていきます。

が、google-github-actions/auth により発行されるアクセストークンを利用する環境変数がプラグイン準備されていなかったので、今はフォークして少し変更しています。本来はGOOGLE_APPLICATION_CREDENTIALSで認証されることを期待していますが、Rubyの認証ライブラリがまだ未対応で、App Distributionのプラグインも対応していません。App Distribution のプラグインでGOOGLE_APPLICATION_CREDENTIALSに対応したいということはこのIssueでも書かれていて、プラグインで対応された場合は、アクセストークンではなくこちらの方法を使うべきです。今は、動かすために、フォークして最低限の変更をしています。

https://github.com/kouki-dan/fastlane-plugin-firebase_app_distribution/commit/8bc321c21f1188d0f54c353387293aa58ddec07c

フォークしたものを使うように、Pluginfileを書き換えます。

# Pluginfile
gem 'fastlane-plugin-firebase_app_distribution', :git => "https://github.com/kouki-dan/fastlane-plugin-firebase_app_distribution.git",
                                                 :branch => "add-FIREBASE_ACCESS_TOKEN-env"

あとは、取得したアクセストークンを、環境変数に与えて完成です。

jobs:
  build:
    # 省略
    - name: Deploy to Firebase App Distribution
      env:
        PERSONAL_ACCESS_TOKEN_GITHUB: ${{ secrets.PERSONAL_ACCESS_TOKEN_GITHUB }}
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
        FIREBASE_ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }}
      run: |
        bundle install
        bundle exec fastlane app_distribution

全体のYAMLファイルは以下のようになりました。

name: Deploy to App Distribution

on: 
  - workflow_dispatch

permissions:
  contents: read
  id-token: write

jobs:
  build:

    runs-on: macos-12
    timeout-minutes: 20

    steps:
    - uses: actions/checkout@v1
    - id: auth
      uses: 'google-github-actions/auth@v1.0.0'
      with:
          workload_identity_provider: projects/595091170546/locations/global/workloadIdentityPools/firebase-app-distribution/providers/github-actions
          service_account: firebase-app-distribution@actionsandappdistributiontest.iam.gserviceaccount.com
          token_format: access_token
    - name: Deploy to Firebase App Distribution
      env:
        PERSONAL_ACCESS_TOKEN_GITHUB: ${{ secrets.PERSONAL_ACCESS_TOKEN_GITHUB }}
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
        FIREBASE_ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }}
      run: |
        bundle install
        bundle exec fastlane app_distribution

これで、SecretにGoogle Cloudの認証情報を保存しなくてもFirebase App Distributionにデプロイできるようになりました!

リポジトリに影響のある変更は、こちらのPRにまとまっています!

github.com

まとめ

これまで外部のAPIを使うためには、共通のシークレットを保存する方法がよく使われていました。OpenID Connet連携を使うことでプラットフォームの認証情報を使い、短いアクセストークンを発行することになります。生存期間の長いシークレットは攻撃に使われる可能性が高くなってしまいがちですが、この方法を使うことにより解消できます。よりセキュアな方法でのAPI連携を目指していきましょう。

*1:本来はアクセストークンがなくとも資格情報ファイルから認証を行えるはずですが、後ほど使うRubyのライブラリで対応しておらず、アクセストークンを発行して使えるようにしています。