Cloud Functions for Firebase / Cloud Storage for Firebase のサンプルコードの署名付き URL がトラップな話

Cloud Functions for Firebase / Cloud Storage for Firebase のサンプルコードの署名付き URL がトラップな話

January 16, 2022

あるとき、Cloud Storage に動画がアップロードされことを起点に Cloud Functions のトリガーを動かし、そのトリガーで FFmpeg を利用してサムネイルを作りつつ、GCP の Transcoder API を呼び出して HLS 形式に動画を変換させる、という処理を作っていました。

Firebase の親切だなと思うところはサンプルコードを用意してくれているところです。自分のやりたいことそのものを実現してくれるようなサンプルはなかなかないですが、局所局所で参考になります。

例えば以下は Cloud Functions for Firebase で FFmpeg を利用してオーディオの変換をしているサンプルです。

で、こちらは Cloud Storage for Firebase に動画がアップロードされたときにサムネイルを生成しているサンプルです。FFmpeg ではなく ImageMagick を使っています。

公式が提供してくれているものなので信頼できますし、ありがたいですね。

ところがどっこい #

上記サンプル、嘘をついているとは言わないまでも、誤解を招きかねない記述があります。それがサムネイルを生成しているサンプルのほうの以下の記述です。

84: // Get the Signed URLs for the thumbnail and original image.
85: const results = await Promise.all([
86:   thumbFile.getSignedUrl({
87:     action: 'read',
88:     expires: '03-01-2500',
89:   }),
90:   file.getSignedUrl({
91:     action: 'read',
92:     expires: '03-01-2500',
93:   }),
94: ]);

動画から生成したサムネイルをバケットに保存して、その画像にアクセスするための URL を取得している部分です。有効期限は 2500 年の署名付き URL を生成しているように見えますね。

つまり、その URL を知らなければアクセスできないようにしつつ、URL の有効期限は実質無期限としているのでしょう。

…というのは理解が間違っていて、実際の有効期限は1週間です。

これは Cloud Storage の仕様で、実際に Cloud Storage のドキュメントにはそれが記載されています。

署名付き URL | Cloud Storage | Google Cloud

署名付き URL の有効期間。X-Goog-Date に格納された値からの秒数で表されます。この例では、署名付き URL の有効期限は 15 分です。有効期間の最大値は 604,800 秒(7 日間)です。

https://cloud.google.com/storage/docs/access-control/signed-urls

つまり、コード上でいくら遠い未来の日付を設定しようが、Cloud Storage の仕様上、署名付き URL は 7 日後に期限切れになります。

それなのに公式のサンプルコードが 2500 年と設定しているのがタチ悪いですよね。誤解を招きかねないです。

対処法 #

本記事はグチが言いたかっただけなので、これで以上にしても良いのですが、念のために私が行った解決策も残しておきます。

有名な方法だとは思いますが、Cloud Storage の公開用 URL を利用する方法です。

まずは Storage のアクセス権限を変更します。私の場合はバケット内のすべてのオブジェクトを公開するように変更しました。

その後、以下のような URL でオブジェクトにアクセスできます。

const bucketName = 'hello-bucket';
const filePath = 'image/world.png';

// こちらの URL でアクセスできます
const publicUrl = `https://storage.googleapis.com/${bucketName}/${filePath}`;

// またはこちらでも同様にアクセスできる様子
const encodedFilePath = encodeURIComponent(filePath);
const publicUrl2 = `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodedFilePath}?alt=media`;

参考

上記参考記事の後者(Stack Overflow のほう)ですが、ベストアンサーではなく以下の回答をしている方の内容が非常に参考になりますので、ぜひご覧ください。

This answer will summarize the options for getting the download URL when uploading a file to Google/Firebase Cloud Storage. There are three types of download URLS:

  1. signed download URLs, which are temporary and have security features
  2. token download URLs, which are persistent and have security features
  3. public download URLs, which are persistent and lack security

しかもこの方の内容をよく見ると、、、

getSignedUrl() for Temporary Download URLs

(中略)

The documentation code has 3-17-2025 as the expiration date, suggesting that you can set the expiration years in the future. My app worked perfectly, and then crashed a week later. The error message said that the signatures didn’t match, not that the download URL had expired. I made various changes to my code, and everything worked…until it all crashed a week later. This went on for more than a month of frustration.

ここにも被害者がいらっしゃいました。