以前GitHub Actionsから、Firbease App Distributionにアプリを配布するエントリを書きました。
エントリを書いた時のFirebase App Distribution認証では、ユーザーのトークンを使っていました。時代は移り、サービスアカウントや、OpenID Connectを使った認証を使えるようになっています。今回は、GitHub ActionsのOpenID Connect対応と Google CloudのWorkload Identityを使ってFirebaseの認証を行う方法を説明します。この方法では、Secretにユーザーのアクセストークンやサービスアカウントのキーファイルを保存する必要がなくなり、よりセキュアに認証を行えます。
- GitHub ActionsのOpenID Connect対応
- Google CloudのWorkload Identityで一時的なアクセストークンを発行
- GitHub Actionsのワークフローを設定する
- まとめ
GitHub ActionsのOpenID Connect対応
詳しくは別の記事を書いたので、詳しく知りたい方はそちらをみてください。簡単に説明すると、GitHub ActionsがジョブごとにOpenID Connectに準拠した認証情報を発行してくれます。
GitHub ActionsでのOpenID Connect対応の詳細についてはこちらのドキュメントを参考にしてください。
Google CloudのWorkload Identityで一時的なアクセストークンを発行
Workload Identityは、外部の認証情報を元にGoogle Cloud Platformへのアクセストークンを発行する仕組みです。
外部の認証情報の1つとして、OpenID Connectをサポートする任意のIDプロバイダを利用できます。任意のIDプロバイダとして、GitHub Actionsを使うことで、GitHub Actionsが発行したIDトークンを、Google Cloud上のアクセストークンに変換できます。
ドキュメントを参考に、Workload Identityを使う準備をしていきます。
上記のドキュメントの通り、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がアクセストークンを発行するために、サービスアカウントを紐づける必要があります。以下のドキュメントに沿って作業します。
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でも書かれていて、プラグインで対応された場合は、アクセストークンではなくこちらの方法を使うべきです。今は、動かすために、フォークして最低限の変更をしています。
フォークしたものを使うように、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にまとまっています!
まとめ
これまで外部のAPIを使うためには、共通のシークレットを保存する方法がよく使われていました。OpenID Connet連携を使うことでプラットフォームの認証情報を使い、短いアクセストークンを発行することになります。生存期間の長いシークレットは攻撃に使われる可能性が高くなってしまいがちですが、この方法を使うことにより解消できます。よりセキュアな方法でのAPI連携を目指していきましょう。
*1:本来はアクセストークンがなくとも資格情報ファイルから認証を行えるはずですが、後ほど使うRubyのライブラリで対応しておらず、アクセストークンを発行して使えるようにしています。