Postgres: RLS のポリシーはアプリが使うロールに切り替えてテストする
スーパーユーザーや BYPASSRLS を持つロールは行レベルセキュリティ(RLS)を素通りするので、SQL エディタで「動いた」ことはポリシーが正しい証拠にならない。SET ROLE でアプリと同じロールになってから確かめる。
使う場面
Supabase や自前の Postgres で RLS を使ってテナント分離・本人のデータだけ見せる制御をしているとき。管理画面の SQL エディタは postgres(スーパーユーザー)で動くことが多く、ポリシーが一つもなくても全行が見えてしまう。
RLS を素通りするのはスーパーユーザーだけではない。
BYPASSRLS属性を持つロール- テーブルの所有者(
ALTER TABLE ... FORCE ROW LEVEL SECURITYを付けない限り)
マイグレーションを流したロールがそのままアプリの接続ロールになっていると、本番でも RLS が効いていないことがある。
やり方
トランザクションの中でロールと JWT のクレームを切り替え、最後に ROLLBACK で戻す(Supabase の例)。
sql
BEGIN;
SET LOCAL ROLE authenticated;
-- auth.uid() などが読む値をセット(第3引数 true でこのトランザクション内だけ)
SELECT set_config(
'request.jwt.claims',
'{"sub":"00000000-0000-0000-0000-000000000001","role":"authenticated"}',
true
);
SELECT * FROM documents; -- 本人の行だけ返るか
UPDATE documents SET title = 'x'
WHERE owner_id <> auth.uid(); -- 0 行になるか
ROLLBACK;未ログインの状態は SET LOCAL ROLE anon; で同じように確かめる。
自前の Postgres なら、アプリ用ロールを所有者と分けて作り、テストでも SET ROLE app_user; してからクエリを流す。所有者のまま使う必要があるなら FORCE ROW LEVEL SECURITY を付ける。
注意点
SET ROLEで切り替えられるのは、今のロールがそのロールのメンバーであるとき(スーパーユーザーなら可)。SET LOCALはトランザクションが終われば元に戻る。BEGINを忘れると、接続を使い回すプールで設定が残ることがある。- 確かめたい項目(他人の行が見えない・書けない・未ログインで 0 行)を SQL のテストにして CI で流すと、ポリシーの書き換えで穴が開いたときに気づける(Supabase なら pgTAP が使える)。
出典
- @james_ailton(Postgres RLS tip)