RLS
Row Level Security
RLSがあると何が嬉しいのか
例: マルチテナントSaaSでの問題
スキーマの構成
想定するのは、複数の企業(テナント)が利用するSaaSアプリケーションです。以下のようなデータベーススキーマを考えます。
sqlCREATE 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クエリとしては以下のようになります。
sqlSELECT * FROM orders WHERE tenant_id = ? AND user_id = ?;
ここで、 ?
の部分にログイン中のユーザーの tenant_id
と user_id
が入ります。
うっかりミスのシナリオ
ここで、開発者が間違って以下のようなクエリを書いてしまう可能性があります。
sqlSELECT * FROM orders WHERE user_id = ?;
このクエリでは、 tenant_id
のフィルターが抜けています。そのため、指定した user_id
に該当する注文が他のテナント(会社)のものであった場合、そのデータも取得されてしまいます。たとえば、ユーザーAがログインしているときに、同じ user_id
を持つ他の会社のデータが見えてしまう可能性があるのです。
RLS(Row Level Security)があれば
PostgreSQLでは、RLSを使って各テナントごとにデータが分離されるようなポリシーを定義できます。例えば、次のようなポリシーを設定できます。
sqlCREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant_id')::int);
このポリシーを適用すれば、 SELECT * FROM orders;
のようなクエリでも、自動的に現在のテナントIDに基づいてデータがフィルタリングされます。これにより、うっかりミスで tenant_id
の条件を忘れた場合でも、他のテナントのデータが漏れることはありません。