1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RLS を書いても塞がらない穴が 3 つあった ── 旅行SNSを作った記録(13)

1
Posted at

Supabase の RLS(行単位のアクセス制御)を書けば守れる、と思っていました。書いたあとに、RLS では塞がらない穴が 3 つあると分かりました。

やったこと(3 ステップ)

  1. RLS の位置づけを「主」から「保険」に書き換える
  2. 権限に関わる列と、RLS を回避する関数に、別の守りを足す
  3. 「ポリシーを書いた」で終わらせず、効いているかを測る

判断:RLS は「主」ではなく「保険」にする

サーバー側の読み書きは、管理者権限の鍵(service_role)で接続しています。この鍵は RLS を迂回します。

迂回するのは設計どおりにしました。理由は、判定を 1 か所に集めたいからです。たとえば「この非公開投稿を見てよいか」はアルバムのメンバーかどうかで決まり、SQL の条件だけでは書き切れません。2 か所に同じ判定を書けば、いつか片方だけ古くなります。

そのかわり、RLS の役目を言い直しました。

主はサーバー側の判定。RLS は「ブラウザから直接 DB を触られたとき」の保険。

ブラウザが直接触るのはログインとファイルのアップロードだけで、そこは RLS が効きます。「二重に守る」は同じ判定を 2 回することではなく、経路ごとに守りを置くことでした。

工夫:2 つの鍵で行数を比べる

「効いているか」はこう測りました。ブラウザに配られる鍵で、Supabase が自動で公開している API を画面を通さずに直接叩く。アプリ越しではないので、直接触られたときと同じ条件になります。

要点は、同じ問い合わせを 2 つの鍵で投げて行数を比べることです。配布される鍵で 0 行でも、それだけでは「隠した」のか「そもそも空」なのか分かりません。管理者権限の鍵で行があると確かめて、初めて「隠した」と言えます。

対象 結果
テーブル 30 件 未ログインで読めた行は 0
└ うち 24 件 データがあるのに 1 行も見えない(= RLS が隠している)
関数 11 件 未ログインで呼べる関数は 0

気をつけたこと:RLS は行しか守れない

RLS は「どの行を見せるか」の仕組みで、「どの列を書き換えてよいか」は決められません。

放っておくと、利用者が自分の行の管理者フラグを自分で立てられます。行の持ち主は本人なので、RLS は通してしまうのです。そこで列単位の権限で、本人が書き換えてよい列を表示名とアイコンだけに絞りました。

気をつけたこと:関数は既定で誰でも呼べる

RLS を回避する目的の関数(security definer)には、もう 1 つ落とし穴があります。PostgreSQL は新しい関数に既定で全員の実行権限を与え、Supabase は公開 API から呼べるようにします。

つまり絞らないと、ブラウザに配る鍵だけで誰でも好きな引数で呼べます。作ったら必ず実行権限をサーバー側だけに絞る、を手順書に入れました。

決めた決まり

  1. RLS は保険。主はサーバー側の判定。 どちらが主かを文書に書く
  2. 書き換えてよい列は、列単位の権限で絞る。
  3. security definer の関数を足したら、実行権限を絞る。
  4. 「書いた」で終わらせず、2 つの鍵で比べて確かめる。

次回

Google Maps の無料枠に収めるために決めたこと。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?