google-cloud-jp
Published in

google-cloud-jp

Cloud Firestoreの勘所 パート3 — セキュリティルール

セキュリティルールでデータを守る🔐

現在のデータ構造

まずは、パート2終了時点でのデータ構造をおさらいすると、次のようになっています。

Firestoreデータ構造

現状のセキュリティルール

セキュリティルールは、パート2で Start in test mode を選んだ結果、次のようにすべてのドキュメントへのあらゆる操作を許可した状態になっています。

準備

パート2の記事の後半で書いた、Firebase CLIのインストール・プロジェクトセットアップなど済んだ前提で進めます。

Visual Studio Codeで、Firestoreのセキュリティルール・インデックスのシンタックスハイライト・コード補完を有効にする

次のプラグインをインストールすると、Visual Studio Codeで、Firestoreのセキュリティルール・インデックスのシンタックスハイライト・コード補完が有効になるので、とてもおすすめです。

一旦セキュリティルールで全不許可にする

それでは、セキュリティルールを更新していきます。次のように if false という条件を加えて、全ドキュメントへのアクセスを明示的に禁止します。

:(コロン)の書き忘れによるデプロイエラー

クライアントから実行

パート2と同じく、Swiftで実行していきます。次のようにルートの posts コレクションを取得しようとすると、

db.collection("posts")
.order(by: "createTime", descending: true)
.getDocuments { (snapshot, error) in
print(error as Any)
snapshot!.documents.forEach { doc in
print(doc)
}
}

[2018/12/13 追記] エミュレーターを用いたローカルテストができるようになりました

Firebase Summit 2018 にてFirestoreエミュレーターの発表・リリースがされました。

  1. firebase serve - only firestore でローカルエミュレーターを起動しておく
  2. 上のドキュメントに従いながら、普通の単体テストと同様に記述・実行

ルートのpostsコレクションのルールを設定

読み取り許可を無条件に与えてみる

次のように /posts/{postId}read を許可すると、先ほどエラーになったアクセスが成功するはずです。

  • get (ドキュメントIDによる個別読み取りを許可)
  • list (コレクション配下のドキュメント一覧読み取りを許可)

認証済みユーザーのみアクセス可能にする

認証済みユーザーのみアクセス可能にするには、次のように request.auth != null の条件を加えます。また、こういった各所で使いそうな処理は関数を定義しておくと良いです。JavaScriptっぽく書くと大体動きます( ´・‿・`)

Auth.auth().signInAnonymously { (user, error) in
print(user)
print(error)
}

ルートのusersコレクションのルールを設定

このままではルートの users コレクションに誰もアクセスできないので、次のように調整します。

db.collection("users").document("mono")
.setData(["name": "もの2"]) { error in
print(error as Any)
}
self.db.collection("users").document(Auth.auth().currentUser!.uid)
.setData(["name": "匿名ユーザー2"]) { error in
print(error as Any)
}

usersへの書き込みデータのバリデーション

先ほどの設定だと、自身の users ドキュメントにあらゆるデータを設定できてしまい、クライアントのバグや悪意ある攻撃などで意図しないデータが設定されてしまうリスクがあります。

  • 条件に応じて各コレクション・ドキュメントへのアクセス許可を制御
  • データ内容を検証して条件を満たさない場合は弾く

users/{userId}/posts サブコレクションのルールを設定

次のように users/{自身のuid}/posts サブコレクションに新規ドキュメントを作成しようとすると、これは権限エラーとなります。まだサブコレクションへのルールが未定義、つまり不許可だからです。

self.db.collection("users").document(Auth.auth().currentUser!.uid)
.collection("posts").document()
.setData([
"title": "匿名ユーザーの記事",
"body": "匿名ユーザーの記事本文",
"createTime": FieldValue.serverTimestamp()
]) { error in
print(error as Any)
}
  • 作成・更新(データの検証付き)
  • 削除
  • create : ドキュメント作成
  • update : ドキュメント更新
  • delete : ドキュメント削除

管理者アカウント制御

応用として、こちらのドキュメントではドキュメントごとに roles を設定する方法が紹介されています。

adminsドキュメントを作成

admins コレクションに管理者権限を与えたいユーザーIDと同一IDのドキュメントを追加しておきます。

existsビルトイン関数でadminsにアクセスしてきたユーザーIDが存在するかチェック

次のように、 isAdmin() がtrueだったら、他人のデータ書き換えを可能としてみます。

db.collection("users").document("mono")
.setData(["name": "もの2"]) { error in
print(error as Any)
}

カスタムクレームを使う別解もあり

上記の方法では、検証のために exists によるFirestoreドキュメントアクセスが発生します。カスタムクレームを使うとそれも不要( auth.token の中身をチェックするだけでよくなる)となります。予めAdmin SDKでカスタムユーザークレームを設定しておく必要があるものの、こちらも良いやり方です。

制限

最後にセキュリティルールの制限にも触れておきます。特に先ほど使った getexists 関数の呼び出し数制限が厳しいことに注意です。またルールの処理の中で実際にFirestoreのクエリがなされるので課金対象になりクエリの処理時間も普通にかかることも一応意識しておくと良いです。

セキュリティルールの制限(2018/10/02時点)

セキュリティルールはどのくらい細かく設定すべき?

ドキュメントDBの性質や上記制限などもあり、特にバリデーションはRDBで制約をガチガチに設定したものほど堅牢にするのは難しく、かつそれを追い求めるとルールもどんどん複雑になってしまうと感じています。

  • 必須: 自分以外のセキュアなドキュメントの読み込みを禁止する
  • 必須: 自分で更新するべきではないドキュメントの書き換えを禁止する
  • 必須: チートをできないようにする(課金情報はクライアントから直接更新できないようにするなど)
  • なるべくやる: 意図しないフィールド・データ種類が書き込まれないようにデータ内容を可能な範囲で検証する(悪意ある攻撃をされても最悪そのユーザーの手元で挙動がおかしくなる程度にとどめられるようにする)

参考資料

公式ドキュメント

Firestoreをプロダクション利用する上では、セキュリティルールに関する公式ドキュメントにすべて目を通すのは必須レベルだと思っています。

記事

詳しく書かれている良い記事です。基本的に公式ドキュメントだけで済むものの、こういう噛み砕いた記事も併読すると理解が捗るはずです。

解説動画

とても良い解説動画です。実際に操作する様子が見えるとイメージが沸きますね。

--

--

Google Cloud Platform 製品などに関連するコミュニティが記載したテクニカル記事集。掲載された意見は著者のものであり、必ずしも Google のものを反映するものではありません。

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store