CloudStorageの署名付きURLに有効期限を設定したはずなのに数日後に403でアクセスできなくなる

# TL;DR

署名付きURLを生成する用のサービスアカウントのキーを明示的に設定する。

# 背景

CloudStorageにアップロードした動画からサムネイル画像を生成したく、
CloudFunctionsでアップロードのトリガーを使い生成する処理を実装していました。

以下のような感じのコードです。
https://github.com/firebase/functions-samples/blob/master/generate-thumbnail/functions/index.js

生成したサムネイル画像はCloudStorageにアップロードし、
署名付きURLを生成してデータベースに保存するようなものを実装していたのですが、
数日後に生成したURLが403で弾かれるようになってしまいました。

ちゃんとサンプルのように有効期限を設定していたのにです。
https://github.com/firebase/functions-samples/blob/8f599e4cc56ef849136982d19f4d9d5348d3dc58/generate-thumbnail/functions/index.js#L84-L92

```javascript
  // Get the Signed URLs for the thumbnail and original image.
  const config = {
    action: 'read',
    expires: '03-01-2500',
  };
  const results = await Promise.all([
    thumbFile.getSignedUrl(config),
    file.getSignedUrl(config),
  ]);
```

# 原因

[Issue](https://github.com/googleapis/nodejs-storage/issues/244)に同様の現象の人がいました。
[こちらのコメント](https://github.com/googleapis/nodejs-storage/issues/244#issuecomment-403601209)によると、

> サービスアカウントのキーは、一定の時間（5～7日）が経過すると削除されます。

とのことです。
CloudFunctionsには一時的なサービスアカウントがあるため、
CloudFunctionsにて署名したURLは「一定の時間（5～7日）」が立ってしまうとマッチしないということでした。
有効期限が短くてよければなんら問題ないのかなとおもいますが、
１日以上にする場合は保険もかねて別途サービスアカウントを明示的に設定してあげる必要がありそうです。

# 対応

[こちらのコメント](https://github.com/googleapis/nodejs-storage/issues/244#issuecomment-403611116)になります。

## 設定手順

### サービスアカウントのキーの作成

1. GoogleCloudConsoleで`IAMと管理`を開きます。
1. 左側のメニューから`サービスアカウント`を開きます。
1. 署名用のサービスアカウントを作成します。
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113439/8aa04f8c-bd52-e611-cd50-7a0063d8f08f.png)

1. 入力できたら作成を押下
  ![スクリーンショット 2020-04-02 13.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113439/63c0dc10-ae1c-da35-e0f2-800387506588.png)


1. ウィザードを進めていくとキーを作成できる画面になるのでここでjson形式で生成して完了を押下
  ![スクリーンショット 2020-04-02 14.01.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113439/64279367-1ff2-9db4-b302-46ba7f258cd9.png)

### IMAの追加

1. GoogleCloudConsoleで`IAMと管理`を開きます。
1. 左側のメニューから`IAM`を開きます。
1. 先ほど追加したサービスアカウントのIAMを追加
1. 以下ロールを設定
  - 「ストレージ管理者」ロールを追加しStorageの操作をできるようにする
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113439/9cb3258a-74cf-7ab2-f02e-ffffb376e185.png)
  - 「サービスアカウントトークン作成者」ロールを追加し署名付きURLを生成できるようにする
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113439/8e5b0518-e946-c30d-aae1-d7fb5dba7a11.png)

### 生成したキーをFunctionsの環境変数に定義

```
// jsonにあるprivate_key
firebase functions:config:set service_account.private_key=xxxxxxxx

// jsonにあるclient_email
firebase functions:config:set service_account.client_email=xxxxxxxx

// 設定されているか確認
firebase functions:config:get
```

### FunctionsのStorageインスタンス生成のオプションを設定

```javascript
// 環境変数を取得
const credentials = {
  client_email: functions.config().service_account.client_email,
  private_key: functions.config().service_account.private_key
}

// キーを設定してインスタンス生成
const gcs = new Storage({ credentials: credentials });

// 以降任意の処理
```

# 結果

この文章が更新されずにのこっているってことはきっと解決できたってことだろう...

# 参考

サービスアカウントのキーの作成
https://cloud.google.com/iam/docs/creating-managing-service-account-keys?hl=ja

Issue
https://github.com/googleapis/nodejs-storage/issues/244
https://github.com/googleapis/nodejs-storage/issues/244#issuecomment-403601209

GCP側の設定
https://qiita.com/harigel/items/665df7b1d806577b32de