Firestoreのセキュリティルールについて調べてみた


はじめに

Firestoreを初見で触ってみたときに、セキュリティルールという存在を知らずプロジェクトを作ってから30日ほどたった時にアクセスができなくなってしまった。

難しそうという印象でとっつきにくかったので概要をまとめてみようと思う。

概念的なポイント

ホワイトリスト形式

基本は外部に対して閉じていて、ホワイトリストに追加されているユーザーがホワイトリストに記載されている権限を使える。記載されていないユーザー、パスにはアクセスできない。

セキュリティルールの構文

セキュリティルールのバージョン

2019年5月からセキュリティルールバージョン2が使用可能になっている。

バージョンによって使える構文や動きが異なる場合があるとのことなので、注意。

バージョンを指定しないとデフォルトでバージョン1になるそう。

指定の仕方は以下。

rules_version = '2';

サービスとデータベースの宣言

基本宣言は以下。

service cloud.firestore {
  match /databases/{database}/documents {
    // ...
  }
}

ルールのスコープを Cloud Firestore とし、Cloud Firestore セキュリティ ルールと Cloud Storage などの他のプロダクトのルールとの間の競合を防ぎます。

Firestore以外にもセキュリティルールを設定できるサービスがあるので、セキュリティルールの混在や競合を防ぐためにこのような宣言が良いらしい。

基本的な読み書きのルール

service cloud.firestore {
  match /databases/{database}/documents {

    // Match any document in the 'cities' collection
    match /cities/{city} {
      allow read: if <condition>;
      allow write: if <condition>;
    }
  }
}

上記のルールが示すのは、/citiesコレクション配下のドキュメントに対するread、writeオペレーションの許可。

match /cities/{city}はワイルドカード使用によるルールで、/cities/SFのように絶対パスで指定することも可能。

より詳細な設定

readとwriteのオペレーションをより詳細なオペレーションに分割できる。

例えば、ドキュメントの作成は認証済みであればOK、削除はルートユーザーのみというような状況もありそう。

  • read
    • get
    • list
  • write
    • create
    • update
    • delete

こんな感じ↓。

service cloud.firestore {
  match /databases/{database}/documents {
    // A read rule can be divided into get and list rules
    match /cities/{city} {
      // Applies to single document read requests
      allow get: if <condition>;

      // Applies to queries and collection read requests
      allow list: if <condition>;
    }

    // A write rule can be divided into create, update, and delete rules
    match /cities/{city} {
      // Applies to writes to nonexistent documents
      allow create: if <condition>;

      // Applies to writes to existing documents
      allow update: if <condition>;

      // Applies to delete operations
      allow delete: if <condition>;
    }
  }
}

階層データ

Cloud Firestoreのデータはドキュメントとサブコレクションにより階層を拡張できる。

セキュリティルールは一致したパスのみに適用されることがポイント。

例えば、以下のような階層のデータに対して、subtaskread権限をアクセス許可するセキュリティルールを記述したい場合。

以下のセキュリティルールの記述だとダメ。

service cloud.firestore {
  match /databases/{database}/documents {
    match /user/{user} {
      allow read, write: if <condition>;
    }
  }
}

正しくは以下。

service cloud.firestore {
  match /databases/{database}/documents {
    match /user/{user} {
      allow read, write: if <condition>;
			
			match /subtask/{subtask} {
          allow read, write: if <condition>;
      }
    }
  }
}

セキュリティルールは一致したパスのみに適用されるので、subtaskまで記述しないとアクセスできない。

再帰ワイルドカード

ルールを任意の深い階層に適用したい場合は以下のようにかけるらしい。

意味としては、userコレクション配下のドキュメントと一致する。

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /user/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

ワイルドカードの記述についてはバージョン差分があるようなので、注意。

matchステートメントの重複

複数のmatchステートメントに一致する場合、いずれかの条件がtrueと評価されるとアクセスが許可される。

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /user/{user} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection or subcollections.
    match /user/{document=**} {
      allow read, write: if true;
    }
  }
}

セキュリティルールの制限

セキュリティルールを記述する上での制限があるよう。公式ドキュメントからはよくわからなかったが、リクエストごとにセキュリティルールが評価されると思うので(多少キャッシュしてスッキップされる評価もあるかもだが)、おそらく以下のような流れで処理しているが故に、セキュリティルールの評価に演算回数食われるとあれっていうのはあるんだろう。

リクエスト発行→セキュリティルールの評価→実データ取得→レスポンス

リクエストあたりの exists()、get()、getAfter() 呼び出しの最大数

単一ドキュメントに対するリクエストとクエリ リクエストの場合は 10。

複数のドキュメントに対する読み取り、トランザクション、一括書き込みの場合は 20。各オペレーションには、前述の上限(10)も適用されます。

いずれかの制限を超えると、アクセス拒否のエラーが発生します。

一部のドキュメントに対するアクセス呼び出しはキャッシュされる場合があります。キャッシュされた呼び出しは制限数に計上されません。

正直よくわからなかったので使ってみて立ち戻ることにする。

関数の呼び出しの深さの最大数

20回。

関数の再帰的な呼び出し、または循環的な呼び出しの最大数

0(許可されていない)。

リクエストあたりの式評価の最大数

1000回。

ルールセットの最大サイズ

64 KB。

終わりに

Firestoreのセキュリティルールについて概要をまとめてみた。

全体的に公式ドキュメントを読むと概要を掴むことは容易な印象だった。

条件式部分では定義したFunctionも使えるようで、JavascriptやGoなどでFunction定義できるようなので、実際にセキュリティルールを記述しながらその辺にも慣れていこうと思う。