要件
- Firebase Storageにアップロードされたファイルへのアクセスを特定のグループに限定したい
条件
- ユーザは所属グループにファイルを投稿(Firebase Storageにアップロード)
- グループの内部の人間のみが投稿したファイルにアクセス可能
- 所属するグループは同時に複数
- 所属グループは頻繁に変わる
FirebaseのSecurity Rule
残念ながらFirebase StorageのSecurity RuleはFirestoreのそれに比べるとできることが非常に少ない。Firestoreの場合、Security Ruleからユーザの所属グループやロールを管理したデータベースにアクセスしてその内容と照会することで、柔軟な制御が可能であった。StorageのSecurity RuleからFirestoreにアクセスはできない。
解決法
1. User Claims
request.auth.token.xxx に任意の情報を付加するCustom Claimsという仕組みがある。adminから追加ができるため、functions等で、ユーザがグループに所属した段階でUser Claims情報(所属グループIDなど)を設定し、Firbase StorgeのSecurity Ruleでそれを参照することで所属グループを信頼性を持って確認できる。
ただ、
- Custom Claimsは反映までに時間がかかる(即時反映ではない).
- 1000Byteまで
- 頻繁に書き換わる権限に対してCustom Claimsを使うべきでないと中の人も言っている
今回の用途には合わない。
2. Metadata
Storage上のファイルには、任意のメタデータを付加することができる。Metadata にアクセスできるユーザの一覧をメタデータに格納して、requst.auth.uidがmetadataに含まれているか確認する
ユーザがグループにJoin/Leave 毎に全ファイルのメタデータを書き換え、全メンバのIDを追加する必要がある。今回の用途では現実的ではない。
3. パス自体を推測不可能なものにする
Storageではgetはallow, list操作はdenyすることができる(Security rule version '2'から) ため、パスが十分に推測不可能な場合には、パスを知らない人間には実質 read不可 となる。グループに属するユーザにのみパスを配布(FirestoreとそのSeurity Ruleでセキュアに可能)することで実現ができる。
ただし、ユーザがグループを脱退した場合、その時点で存在したファイルには脱退後も引き続きアクセスできてしまう(パスを知っているから)。この条件が許容できる場合のみ適用可能。
パスの生成/アップロード
Option 1 : クライアントでパス生成&アップロード
一番、帯域・時間がかからない。パス生成はUUID生成ライブラリ等を使う。但し「クライアントは信頼できない」が基本であり、悪意のあるパス、バカチンなパスを生成するクライアントがいる場合を考える必要あり。
Option 2 : サーバサイドでパス生成&アップロード
Functions等をかましてサーバでパス生成アップロードまで含めて実施する。
最もセキュアな気がするが、1 Callで扱えるのは10MBまでのファイルとなる上、ファイルはクライアント→Function->Storageという経路を辿るため、アップロード量が2倍となるため時間がかかる。
Option 3 : サーバサイドでパス生成、アップロードはクライアント
サーバサイドで生成したパスを無視するバカチンがいる可能性もある。その意味でOption 1と変わらない。
Option 4 : サーバサイドでパス生成、アップロードはクライアント, サーバサイドで生成したパスにしかアップロードできない制限付加
Firebase storageではファイルの上書きが可能である。上書きされた場合、metadataのupdatedフィールドがtrueになるようである。そこで思いつくのが、以下の方法。
- サーバサイドでURL生成
- サーバサイドでURLに対してダミーの小さなファイルをアップロードする
- Security Ruleは metadata.updated == falseの場合のみwrite属性をつける (サーバサイドは特権あるので最初のアップロードも可能). metada.updatedは書き換えできないものなので安全。
まとめ
「アクセス制御されている」と言い切れない部分があるため、何となく気持ち悪いが、今回の用途では 3. しか選択肢がない。
StorageのSecurity Ruleをもう少し細かく設定できるようにして欲しい。