Cloud Functionsのサービスアカウントを指定しよう。

この記事は「[ソニックガーデン プログラマ - Qiita Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden)」21 日目の記事です。

---

# はじめに

Cloud Functions for Firebase（以下 Functions） は実行する際にサービスアカウントというアカウントを用いて実行されます。これは一般的な Google Cloud のユーザーではなく、Google Cloud のサービスを実行する専用の特別なアカウントで、固有のメールアドレスを持っています。

Functions をから Firebase のサービスを利用する場合は Firebase Admin SDK を使うので意識しなくてもよいですが、Google Cloud の API にアクセスしようとすると Functions を実行しているサービスアカウントに Google Cloud の API を実行するための権限を追加で付与する必要がある場合もあります。

# Functions で使われるサービスアカウント

デフォルトで使用されるサービスアカウントは Functions の第 1 世代・第 2 世代で異なります。それぞれ以下のサービスアカウントが実行時に利用されます。

- Functions 第 1 世代: `[プロジェクトID]@appspot.gserviceaccount.com`
- Functions 第 2 世代: `[プロジェクトID(数値)]-compute@developer.gserviceaccount.com`

このように実行する Functions の世代によって使われるサービスアカウントは異なりますが、権限としてはどちらも「編集者」ロールが付与されているので、できることは同じになります。

では「編集者」ロールでは何ができるのか確認してみると、なんと 9133 個もの権限が付与されています。説明にもある通り「ほとんどの Google Cloud リソースを表示、作成、更新、削除」できるそうです。

![CleanShot 2024-12-19 at 21.25.23@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204683/917d1e35-44f3-8ce2-c430-b3e9a6e42e05.png)

つまりデフォルトのサービスアカウントでほとんど全てのことができるので問題ないですね！
...とはなりません。

このサービスアカウントのアクセスキーが万が一漏れてしまった場合、「ほとんどの Google Cloud リソースを表示、作成、更新、削除」される可能性があります。そもそも Functions を動かすにあたって、**9133** 個の権限は明らかに過剰です。必要最小限の権限で Functions を動かすようにすれば、もし万が一アクセスキーが漏れてしまっても影響範囲を最小限にすることができるので、不要な権限は持たせないようにしましょう。

# サービスアカウントを作成する

サービスアカウントは自分で作成することもできます。
Google Cloud の[IAM と管理] - [サービスアカウント]を開き、「サービスアカウントを作成」ボタンから作成することができます。

![CleanShot 2024-12-19 at 21.45.34@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204683/b001b94e-52d4-33b5-42eb-ea5aaeed42a1.png)

作成後、ロールを設定できますが一旦なにも設定せずに完了します。

![CleanShot 2024-12-19 at 21.50.29@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204683/9e5865ef-d3be-1ae4-a21b-21189698542a.png)

これでまっさらなサービスアカウントを作成することができました。

# 作成したサービスアカウントを Functions で利用する

作成したサービスアカウントを使用して Functions から BigQuery にアクセスしてみましょう。
以下のように、BigQuery の Dataset の有無を返す単純な onRequest 関数を作成してみます。[^1] この時、onRequest のオプションに`serviceAccount`として先ほど作成したサービスアカウントのメールアドレスを設定します。これでこの関数を実行する際は指定したサービスアカウントで動作するようになります。

```ts
import { BigQuery } from '@google-cloud/bigquery';
import { onRequest } from 'firebase-functions/v2/https';

export const datasetExists = onRequest(
  { serviceAccount: 'cloud-functions@[プロジェクトID].iam.gserviceaccount.com' },
  async (request, response) => {
    const bigQuery = new BigQuery();
    const dataset = bigQuery.dataset('sample_dataset');
    const [isExists] = await dataset.exists();
    response.status(200).send(isExists);
  }
);
```

この関数を実行するとエラーになります。エラーメッセージにあるように`bigquery.datasets.get`権限が不足していますね。

```
Error: Access Denied: Dataset [プロジェクトID]:sample_dataset: Permission bigquery.datasets.get denied on dataset [プロジェクトID]:sample_dataset (or it may not exist).
```

サービスアカウントに`bigquery.datasets.get`権限を含むロールを付与してあげましょう。ここでは「BigQuery データ閲覧者」を付与します。

![CleanShot 2024-12-19 at 22.29.36@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204683/2345a8a2-583f-859e-c269-cf4835887db5.png)

この後、改めて関数を実行すると`true`が返ってきて、BigQuery へのアクセスができたことがわかります。

# まとめ

Functions では`serviceAccount`に自作のサービスアカウントを設定し、必要最低限の権限（ロール）を付与しましょう！

---

次回、「[ソニックガーデン プログラマ - Qiita Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden)」22 日目は @interu です。お楽しみに！

[^1]: 事前に Google Cloud で BigQuery API を有効にし、`sample_dataset`というデータセットを作成しておきます。