RLS
Row Level Security
GPT-4.icon
RLSがあると何が嬉しいのか
例: マルチテナントSaaSでの問題
スキーマの構成
想定するのは、複数の企業(テナント)が利用するSaaSアプリケーションです。以下のようなデータベーススキーマを考えます。
code:sql
CREATE TABLE tenants (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id INT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id INT,
user_id INT,
product_name VARCHAR(255),
quantity INT,
FOREIGN KEY (tenant_id) REFERENCES tenants(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
このスキーマでは、tenants テーブルが各企業を、users テーブルがその企業に所属するユーザーを、orders テーブルが注文情報をそれぞれ保持しています。
典型的な操作
通常、SaaSアプリケーションでは、ユーザーがログインして自分の会社のデータ(orders)を見たいと考えます。SQLクエリとしては以下のようになります。
code:sql
SELECT * FROM orders WHERE tenant_id = ? AND user_id = ?;
ここで、? の部分にログイン中のユーザーの tenant_id と user_id が入ります。
うっかりミスのシナリオ
ここで、開発者が間違って以下のようなクエリを書いてしまう可能性があります。
code:sql
SELECT * FROM orders WHERE user_id = ?;
このクエリでは、tenant_id のフィルターが抜けています。そのため、指定した user_id に該当する注文が他のテナント(会社)のものであった場合、そのデータも取得されてしまいます。たとえば、ユーザーAがログインしているときに、同じ user_id を持つ他の会社のデータが見えてしまう可能性があるのです。
RLS(Row Level Security)があれば
PostgreSQLでは、RLSを使って各テナントごとにデータが分離されるようなポリシーを定義できます。例えば、次のようなポリシーを設定できます。
code:sql
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant_id')::int);
このポリシーを適用すれば、SELECT * FROM orders; のようなクエリでも、自動的に現在のテナントIDに基づいてデータがフィルタリングされます。これにより、うっかりミスで tenant_id の条件を忘れた場合でも、他のテナントのデータが漏れることはありません。