
Firebase 関連プロダクトを GitHub Actions から安全にデプロイする
こんにちは。Build サービス推進チームで Solution Architect をしている t_maru です。
今回は Firebase に開発したアプリケーションなどをデプロイしていくために必要な権限について解説します。
Firebase と GitHub Actions
まず今回取り上げる Firebase とは何かという点ですが、こちらは Google が提供しているクラウドサービスで、各種アプリケーションが必要とする様々なバックエンドのサービス群です。このため、BaaS (Backend as a Service) と呼ばれていたりもします。
Firebase には DB や Functions の機能以外にも、モバイルアプリに Push 通知を送る機能であったり、アプリケーション上のユーザーの行動を分析するための機能 (Google Analytics と連携した機能)、Web アプリケーションをホスティングする機能など、非常に多くのサービスがあるだけではなく、これらを簡単にアプリケーションに組み込むことや CLI からリソースの操作をすることができるため、スモールスタートで早くリリースをしていきたい場合などに非常に適しているサービスだと思います。
また、各種サービスもマネージドものが多く、リソースのスケーリングなども裏側で自動的に行ってくれるのでスモールスタートで始めて、徐々に大きくしていくといったケースでも利用者数の増加に柔軟に対応できるため、様々なシステムを Firebase 上に構築することができると思います。
Firebase の詳細については、下記の公式サイトをご覧ください。
https://firebase.google.com/?hl=ja
次に GitHub Actions です。
GitHub に関してはは皆さんよくご存知の(?)、git でバージョン管理されたソースコードをホスティングすることのできるサービスです。GitHub にも企業のユースケースに合わせた GitHub Enterprise というサービスプランもありますが、今回扱うのはインターネット上で使用することのできるクラウドサービスです。
この GitHub 上に保存したソースコードですが、保存されるタイミングや Pull Request 作成などのイベントをトリガーとしてワークフローを実行することができます。この機能のことを GitHub Actions と呼びます。
GitHub Actions の詳細については、下記の公式サイトをご覧ください。
https://github.co.jp/features/actions
Firebase に GitHub Actions からデプロイする
今回は以下のようなケースを例に考えたいと思います。
Firebase Hosting 上に Web アプリをデプロイ
Firestore を DB として利用
Firebase Storage を Object storage として利用
Firebase Functions を使ってビジネスロジックを実行 (API として使用)
GitHub Actions の Marketplace を検索すると Deploy to Firebase Hosting など、既に様々なワークフローが公開されていたりもしますが、今回は GitHub Actions のコンテナ内から Firebase CLI を使って必要なリソースをデプロイする場合を考えます。
Firebase へのデプロイは CLI を使うと非常に簡単に実行することができます。設定済みの各種プロダクトのデプロイを全て一括で行う場合は下記のコマンドを叩くだけで全てが完了します。(事前に Firebase CLI のインストールが必要です。)
firebase deploy --project <対象の Firebase (GCP) プロジェクト ID>
一括でなく特定のサービスのみデプロイを行いたい場合、例えば、Hosting のデプロイだけ実行したい場合は下記のコマンドを使うなど、用途に応じて様々な指定が可能になっています。
firebase deploy --only hosting --project <対象の Firebase (GCP) プロジェクト ID>
今回取り上げている GitHub Actions からのデプロイに関しても、Marketplace で提供されているワークフローを使わない場合は、Firebase CLI を使ったデプロイが一番シンプルで良いかと思います。
補足ですが、上記のコマンドのオプションで指定している --project に関しては、デフォルトのプロジェクトを CLI に設定済みであれば記載不要ですが、複数のプロジェクトを利用している場合にはデプロイ先を明確に記載したほうがデプロイ先を間違えたなどのトラブルが起きにくいと思います。
安全にデプロイするための権限設定
Firebase CLI を開発者が Local PC で使う場合には通常 `firebase login` というコマンドを叩いて、開発者の Google アカウントで認証するやり方が一般的かと思います。
これを行うことで、開発者に割り当てられている権限がデプロイ時に評価され、デプロイ対象のプロダクトに対する権限を開発者が持っている場合は正常にデプロイを行うことができます。
GitHub Actions から Firebase CLI を使ってリソースのデプロイをする場合においても、デプロイ対象のリソースに対する権限がなければデプロイ時にエラーとなります。
CI/CD のコンテナ上からどうやって Google アカウントでログインするんだ?と思うかもしれませんが、CI システム上での利用も想定されているので Firebase CLI には下記のコマンドが存在します。
firebase login:ci
このコマンドを実行すると、firebase login のとき同様に Google アカウントを指定してログインをすることができ、その結果としてトークンが発行されます。これを CI システムの環境変数に `FIREBASE_TOKEN` という名前で設定しておくか、Firebase CLI を実行する際に `--token` オプションを使って発行されたトークンを渡すことで、トークンに紐づく権限で許可された操作を CI コンテナ上のプログラムから実行することができるようになります。
GitHub Actions からデプロイしたいという目的はこの token を使用することで解決することができるのですが、何が問題となるのでしょうか?
結論から言うと、このトークンに割り当てられている権限が過剰な可能性が高いという点です。
先程の説明にもあったように、このトークンは開発者の Google アカウントの権限に従ったものです。会社や組織のポリシーで、開発者に割り当てられている権限はある程度絞られていると思いますが、今回扱っている Firebase へのデプロイに必要な権限以外も含まれていることがほとんどではないでしょうか。
例えば、GCP の事前定義済みの権限のうち、プロジェクトに紐づくものではオーナー、編集者、閲覧者などがあります。この権限はプロジェクト全体に対する権限のため、プロジェクトの編集者が設定されている場合、GCP プロジェクトのほとんどのリソースが編集、つまり設定変更できることになります。
先程 Firebase CLI により発行したトークンは、いつでも revoke することはできますが、仮にこのトークンが外部に漏れてしまい、それに気づかなかった場合は攻撃者に GCP 上のリソースを好き勝手使われてしまう危険性があります。このため、必要な権限のみに絞った設定が重要になってきます。(Cloud を使う場合によく言われる、最小権限で設定しましょうということです。)
GitHub Actions からデプロイするための権限
GitHub Actions から Firebase Hosting, Firebase Functions, Firebase Firestore, Firebase Storage へアプリケーション、設定を安全にデプロイするための権限設定と、CI コンテナ上での使用方法について解説します。
前項で出てきたトークンによる認証は、Google アカウントでないと利用できないため GCP のサービスアカウント を使った方法で権限の割当を行います。詳細は下記のドキュメントを参照していただければと思いますが、簡単に説明すると、Google アカウントとしてログインはできませんが、Google アカウントのユーザーと同様に権限を割り当てることのできるリソースで、GCP リソースの操作に使用することのできる特別なアカウントです。
サービスアカウントについての詳細は、下記のドキュメントをご覧ください。
https://cloud.google.com/iam/docs/service-accounts?hl=ja#what_are_service_accounts
今回、例として上げている各種プロダクトにデプロイを実行するためには、GCP の下記の権限が必要になります。
roles/firebase.developAdmin
roles/cloudfunctions.admin
roles/cloudscheduler.admin (Functions を指定日時で起動する場合に必要)
roles/iam.serviceAccountUser
まず、サービスアカウントを作成し、JSON 形式でキーをダウンロードしておき、その後、上記の権限を gcloud コマンドまたは GCP のコンソールを使ってサービスアカウントに設定します。
下記は権限設定のコマンドを列挙した形になっておりますが、必要に応じてサービスアカウント作成なども含めてスクリプト化して利用していただくとより効率的になると思います。
下記のコマンドにおいて、`<PROJECT_ID>` と `<SERVICE_ACCOUNT>` はご自身のアカウントの情報に書き換えてご利用ください。
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member=serviceAccount:<SERVICE_ACCOUNT> \
--role=roles/firebase.developAdmin \
--project=<PROJECT_ID>
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member=serviceAccount:<SERVICE_ACCOUNT> \
--role=roles/cloudfunctions.admin \
--project=<PROJECT_ID>
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member=serviceAccount:<SERVICE_ACCOUNT> \
--role=roles/cloudscheduler.admin \
--project=<PROJECT_ID>
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member=serviceAccount:<SERVICE_ACCOUNT> \
--role=roles/iam.serviceAccountUser \
--project=<PROJECT_ID>
権限の設定が済んだら GitHub Actions でこのサービスアカウントを使うための設定を行います。
大まかな流れは下記のようになります。
サービスアカウントの JSON ファイルを Base64 エンコードする
Base64 したサービスアカウントの JSON キーを GitHub のコンソールから Actions の Secret として登録
GitHub Actions の定義ファイル (.github/workflows/ ディレクトリに作成する yml ファイル) 内でスクリプトにてサービスアカウントの JSON ファイルを復元し、環境変数に設定する
サービスアカウントの Base64 エンコードは、お使いの PC のコマンド等を使い実施してください。
次の GitHub への Secret 登録は、リポジトリ単位で行うことになります。登録を行うメニューは下記の順番でたどっていくとたどり着くことができると思います。(2022/9 現在の GitHub の UI を参考にしているため、皆様がご覧になったときには違う場所にメニューがあるかもしれませんが、その点はご了承ください。)
リポジトリの `Settings` タブ -> サイドメニューの `Secrets` -> `Actions` -> `New repository secret`
今回は Secret の name を `SERVICE_ACCOUNT` とします。
次に workflow の設定ファイルですが、下記にサンプルを記載します。
name: Deploy to environment
on:
push:
branches:
- main
jobs:
test_and_deploy:
timeout-minutes: 15
runs-on: ubuntu-latest
env:
GOOGLE_APPLICATION_CREDENTIALS: ./service-account.json
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Update node
uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'yarn'
- name: Restore Service Account
working-directory: ./
run: |
echo ${{ secrets.SERVICE_ACCOUNT }} | base64 --decode > service-account.json
- name: Unit test on firestore
working-directory: ./firestore
run: |
yarn install
yarn test:ci
- name: Unit test on storage
working-directory: ./storage
run: |
yarn install
yarn test:ci
- name: Build & unit test on web
working-directory: ./web
run: |
yarn install
yarn test:ci
yarn build:debug
- name: Build & unit test on functions
working-directory: ./functions
run: |
yarn install
yarn test:ci
yarn build
- name: Prepare deploy
working-directory: ./
run: |
yarn install
- name: Deploy backend
working-directory: ./
run: |
yarn firebase deploy --only firestore,storage,functions --project some-debug-project --force
- name: Deploy frontend
working-directory: ./
run: |
yarn firebase deploy --only hosting --project some-debug-project --force
このファイルで注目していただきたい個所は 2 点です。
1 点目は `GOOGLE_APPLICATION_CREDENTIALS` という環境変数の設定です。
env:
GOOGLE_APPLICATION_CREDENTIALS: ./service-account.json
この環境変数により、Firebase CLI を呼び出しに使われるサービスアカウントの情報が記述されているファイルのパスを指定できます。
2 点目は上記の環境変数に指定したファイルパスに、Base64 したサービスアカウントのキーを復元する部分です。
- name: Restore Service Account
working-directory: ./
run: |
echo ${{ secrets.SERVICE_ACCOUNT }} | base64 --decode > service-account.json
これにより、環境変数で設定したパスにサービスアカウントのキーが復元されるため、Firebase CLI の deploy コマンドが呼び出される際に指定したサービスアカウントが使用されるようになります。
まとめ
今回は、GitHub Actions から Firebase の各種サービスにデプロイを行う場合の権限設定について解説しました。
`firebase login:ci` で生成されるトークンは面倒な設定が不要なので便利ではありますが、デプロイには必要ない権限が多く付与されている可能性がありますのでご注意ください。
今回紹介したサービスアカウントを使った方法で、権限を必要最小限のものに限定することでより安全に Firebase への CI/CD を行えるようになると思いますので、参考にしていただければ幸いです。