Pulumi で Firebase のリソースを作るために必要な設定

#### 2021-04-13 追記

後日 GCS のバケットを新しく作成しようとしたらできませんでした。（削除はできた気がしたんですが、幻覚を見ていたのかも...）
また `pulumi refresh` を実行しようとすると、サービスアカウントの借用をしないと Firebase 関係のリソースが見れず、借用をするとそれ以外のリソースが見れないという事態に陥りました。
そのため `auth/impersonate_service_account` を unset して  `GOOGLE_OAUTH_ACCESS_TOKEN` 環境変数を消し、 `pulumi state delete` で Firebase のリソースを Pulumi の管理から外しました。
現時点では「Firebase を IaC ツールで管理しない方が良い」と思っています。

# 概要

IaC ツールである [Pulumi](https://www.pulumi.com/) を使って [Firebase](https://firebase.google.com/?hl=ja) のアプリを作ろうとしたら googleapi から 403 エラーが返ってきたので、それを解消する方法を調査しました。
インフラ初心者が場当たり的に調べた結果なので、もし内容がおかしかったらコメントで教えてください。

# 前提

- pulumi CLI のバージョンは 2.24.1
- 使用言語は TypeScript
    - @pulumi/gcp のバージョンは 4.0.0
    - @pulumi/pulumi のバージョンは 2.0.0
- gcloud へのアクセスには僕個人の Google アカウントを使用
    - GCP プロジェクトへのオーナー権限を所有
- Firebase プロジェクトはコンソールから作成済み
    - GCP プロジェクトに紐付けてある

# 発端

Pulumi で以下のようなコードを実行しようとしたところ、

```typescript
import * as gcp from "@pulumi/gcp";

new gcp.firebase.WebApp("my-firebase-webapp", {
  displayName: "my-app",
});
```

以下のエラーが発生しました。

```
  gcp:firebase:WebApp (my-firebase-webapp):
    error: 1 error occurred:
    	* Error creating WebApp: googleapi: Error 403: Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the firebase.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.
    Details:
    [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/999999999999",
          "service": "firebase.googleapis.com"
        },
        "reason": "SERVICE_DISABLED"
      }
    ]
```

どうやら `the auth/impersonate_service_account setting` 、即ちサービスアカウントの権限を借用しないといけないようです。
（ `the billing/quota_project setting` の方は良くわかりませんでした。）

# 解決方法

## 1. firebase-adminsdk サービスアカウントの権限を借用する

Firebase のドキュメントには、

> Firebase では、ユーザー認証情報を共有せずに、サービス アカウントを使用してサービスの操作と管理を行えます。Firebase プロジェクトを作成すると、プロジェクトで複数のサービス アカウントが用意されます。

https://firebase.google.com/support/guides/service-accounts?hl=ja

とあります。
また Firebase のコンソールの「プロジェクトの設定」 > 「サービス アカウント」には、

> Firebase サービス アカウントを使用し、Database、Storage、Auth などの複数の Firebase 機能を、統合 Admin SDK を介してプログラムにより認証できます。
> 
> Firebase サービス アカウント
> firebase-adminsdk-xxxxx@my-gcp-project.iam.gserviceaccount.com

とあります。
GCP のサービスアカウント一覧を見てみると、確かに firebase-adminsdk なるサービスアカウントが作られているようです。
というわけでこのサービスアカウントの権限を借用します。
借用の方法は以下の通り。

> 「gsutil -i」、「gsutil config」を実行して boto 構成ファイルを編集するか、「gcloud config set auth/impersonate_service_account」を実行して、なりすましに使用するサービス アカウントを指定できます。
> 
> なりすましを行うには、元の認証情報で、ターゲット サービス アカウントに roles/iam.serviceAccountTokenCreator を付与する必要があります。 詳細については、次をご覧ください。

https://cloud.google.com/storage/docs/gsutil/addlhelp/CredentialTypesSupportingVariousUseCases?hl=ja

まず `auth/impersonate_service_account` をセットします。

```
$ gcloud config set auth/impersonate_service_account firebase-adminsdk-xxxxx@my-gcp-project.iam.gserviceaccount.com
```

次に借用する側のアカウント（今回は僕の個人 Google アカウント）に `roles/iam.serviceAccountTokenCreator` （サービス アカウント トークン作成者）を付与します。
自分は GCP のコンソールからやりました。

## 2. 環境変数を設定する

上記だけだとうまく行かず、色々調べた結果↓の記事にたどり着きました。

> As for Terraform, set the GOOGLE_OAUTH_ACCESS_TOKEN variable to pass an OAuth2 token:
> 
> export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)

https://medium.com/dev-genius/simple-gcp-authentication-with-service-accounts-6b877c2e2649

自分は [direnv](https://github.com/direnv/direnv) を使っているので、.envrc に以下を追記しました。

```shell:.envrc
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
```

この状態で `pulumi up` を実行したところ、無事 Firebase アプリが作成されました。
ちなみに GCS バケットの削除など Firebase と関係ないリソースの操作もできました。
操作のログは↓のような感じになります。

```
authenticationInfo: {
  principalEmail: "firebase-adminsdk-xxxxx@my-gcp-project.iam.gserviceaccount.com"
  serviceAccountDelegationInfo: [
    0: {
      firstPartyPrincipal: {
        principalEmail: "＜僕個人の Google アカウントのメールアドレス＞"
      }
    }
  ]
}
```

以上です。