自分用メモ。
調査せずに雑に書いてるので、テキトーに見てください。
最後らへんは、実機に試さずに公式ドキュメントを見て「多分こうだろうな」という感じで書いたので、かなり間違ってる可能性があります
マルチテナントとは?
ざっくりいうと、「複数の会社の情報がDBに入ってるよ~」なこと。
マルチテナントアーキテクチャとは?
「複数の会社の情報がDBに入ってるよ~」な状態のとき、
「こうやって複数の会社の情報を管理すれば良い感じなんじゃね?」な設計のこと。
たとえば、👇などがあるらしい。
- 「会社ごとにサーバーを分けちゃおうぜ」な方法
- 「会社ごとにDBを分けちゃおうぜ」な方法
- 「会社ごとにテーブルを分けちゃおうぜ」な方法
- 「全部の会社を1つのテーブルで管理しようぜ」な方法
たとえば、会社Aと会社Bがある場合、それぞれ👇みたいな感じになります。
- 「会社ごとにサーバー自体を分けちゃおうぜ」な方法
- 会社A用のサーバーを建てて、その中でPostgreSQLのようなDBを動かしたり。
- 会社B用のサーバーを建てて、その中でPostgreSQLのようなDBを動かしたり。
- 「会社ごとにDBを分けちゃおうぜ」な方法
- 会社A用のDBと、会社B用のDBをつくって、それぞれにAccountテーブルみたいなテーブルが存在する
- 「会社ごとにテーブルを分けちゃおうぜ」な方法
- DBは1つだけど、会社A用のAccountテーブル、会社B用のAccountテーブルがある・・・みたいな感じ。
- 「全部の会社を1つのテーブルで管理しようぜ」な方法
- DBは1つだし、会社Aと会社Bで共通のAccountテーブルを使う
要件にもよると思いますが
多くの場合、1番最後のパターンでPostgreSQLのRLS(Row Level Security)という機能を使うとコスパが良いらしい。
大体のWEBアプリってマルチテナントじゃないの?
たぶん👇のような違いがあるのかも。
- 普通の個人が使うアプリ(たとえばメルカリとかツイッターみたいな)
- 情報を漏洩させたとしても、個人情報が漏れるだけ。
- なので、「お前らが漏洩させたせいでうちは大損害だ!」みたいなことにならない。
- なので漏洩しても「ダメな会社だな」と思われるだけで済む。
- 会社が使うアプリ(たとえばSlackみたいな)
- 情報を漏洩させると、会社の情報が漏れる。
- 会社からすると、「お前らが漏洩させたせいでうちは大損害だ!」みたいなことになるかも。
- なので漏洩させると会社が終わるかも。
なので、前者のようなアプリでは「マルチテナントなんて知らねーぜ!」なノリで開発できるけど
後者のようなアプリでは、マルチテナントを意識しないと最悪会社がつぶれちゃうので、意識せざる得ないって感じなのかも。
RLSとは?
テーブルのレコード単位で「アクセスしちゃダメ!」とかを制御できる機能のこと。
この機能は、以下のDBにしか備わっていないらしい(軽く調べただけなので間違ってるかも)。
- PostgreSQL
- Oracle
- SQL Server
レコード単位ではなくて、テーブル単位で「アクセスしちゃダメ!」とかを制御できる機能は、SQL標準で用意されています。
これはGRANTコマンドで、👇のような感じで書けます。
GRANT SELECT, INSERT ON accounts TO TANAKA;
この場合、「TANAKAはaccountsテーブルに対してSELECT, INSERTができるよ~」な意味になります。
ちなみにTANAKAの部分は、ロールも指定できます。
ロールは、「このユーザーはこんなことができるよ~」を設定できる単位です。たとえばAdminというロールをつくって「TANAKAにAdminロールを付与する!」みたいにできます。あと「ロールAに対してロールBを付与する」みたいなこともできるらしい。
(この辺は今回の主題ではないのでこの辺で)
つまり、👇のような違いになります。
- GRANTコマンド:
- テーブル単位で「アクセスしちゃダメ!」とかを制御できる
- RLS:
- レコード単位で「アクセスしちゃダメ!」とかを制御できる
要するに
- 「全部を会社を1つのテーブルで管理しようぜ」な方法
を実現するために「RLSを使うと良い感じに行けるんじゃね?」ってことらしい。
RLSを使うには?
RLSを使うには、👇のようなSQLを使います(公式から引用)。
CREATE TABLE accounts (manager text, company text, contact_email text); CREATE POLICY account_managers ON accounts USING (manager = current_user); ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
1行目で、RLSを適用したいテーブル「accounts」を作成します。
次に2行目で「account_managers というポリシーを作ってaccoutsテーブルに適用する。閲覧/更新/削除はユーザー名とmanagerカラムと一致するユーザーだけ許可する。作成は条件なしに誰でも許可する。」とできます。
最後に3行目で「このテーブルのRLSを有効にする!」というのを宣言します。
これを書かないと、RLSが有効にならず、「ポリシーは設定されてるけど無視される」な状態になります。
これが基本文法です。
RLSを無効にする
ポリシーを付与したテーブルに対して、👇を実行すると「このテーブルのRLSを無効にする!」とすることもできます。
ALTER TABLE accounts DISABLE ROW LEVEL SECURITY;
この場合、付与されたポリシーは無視されるようになりますが、削除はされません。
RLSの例外
テーブル所有者は、RLSに関係なくアクセスできます。
MySQLなどには、所有者という概念がないっぽいので、これはPostgreSQLだけ(?)の機能なのかも🤔
複数のポリシーが存在する場合
ポリシーのどれかが「アクセスを許可するゥ」となれば、その行はアクセス可能になります。
逆に、ポリシーが1つもないのに、RLSが有効になっている場合、テーブル所有者以外はアクセスできなくなります。
誰に適用するか?
TO PUBLICと付けると「全員に適用せよ」となります。これは省略できます。
なので👇は等価です。
CREATE POLICY account_managers
ON accounts
USING (manager = current_user);
CREATE POLICY account_managers
ON accounts
TO PUBLIC
USING (manager = current_user);
👇のように書くと「mangersロールにだけ適用する」ができます。
CREATE POLICY account_managers
ON accounts
TO managers
USING (manager = current_user);
なのでこの場合「mangerロールが前提。その上で、自分のユーザー名とmanagerカラムと一致するユーザーだけ許可する。作成は条件なしに許可する。」とできます。
どのコマンドに適用するか?
FOR { ALL | SELECT | INSERT | UPDATE | DELETE }句とつけると、特定のコマンドだけ許可できます。
何も指定しない場合、ALLになります。
なので👇の2つは等価です。
CREATE POLICY account_managers
ON accounts
USING (manager = current_user);
CREATE POLICY account_managers
FOR ALL
ON accounts
USING (manager = current_user);
たとえば👇のように書いた場合、「閲覧はユーザー名とmanagerカラムと一致するユーザーだけ許可する。その他は許可しない。」とできます。
CREATE POLICY account_managers
FOR SELECT
ON accounts
USING (manager = current_user);
USING、WITH CHECK、FORの違い
全部の文法をまとめたのが以下です。
CREATE POLICY name ON table_name [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [ WITH CHECK ( check_expression ) ]
USING、WITH CHECK、FORはそれぞれ以下のように違います。
- FOR:どのコマンドを許可するか
- USING:閲覧/更新/削除を許可するか(チェックが通らなかった行は無視されるだけ)
- WITH CHECK:作成/更新を許可するか(チェックが通らなかった行はエラーになる)
まずFORで「そのコマンドが許可されてるか」をチェックして、次にUSINGかWITH CHECHかでチェックされます。
たとえば、以下みたいな感じ。
- FOR INSERTと書いた場合
→「INSERTだけが許可されて、INSERTを実行しても良い行かどうかWITH CHECKでチェックされる」みたいな感じ。 - FOR SELECTと書いた場合
→「SELECTだけが許可されて、SELECTを実行しても良い行かどうかUSINGでチェックされる」みたいな感じ - FOR UPDATEと書いた場合
→「UPDATEだけが許可されて、UPDATEを実行しても良い行かどうかUSINGとWITH CHECKでチェックされる」みたいな感じ
UPDATEだけが両方でチェックされるのは、UPDATEは「更新する行を”選択”して”更新”する」という、いわば閲覧と更新を同時に行う処理だかららしいです。
ちなみにUSING句、WITH CHECK句、FOR句は省略できます。
なので以下のように書くと、「全員何してもOK」という意味のないポリシーになります。
CREATE POLICY account_managers ON accounts
まとめ
RLSを一言で言うなら「自動的にwhere = “hoge”をつける機能」というだけの機能な気がします。
要するに、「アプリ側ではなくて、より低レイヤーであるDB側で制限を作っちゃってミスが出ないようにしよう」的な技術がRLSですね。
おわり
参考にした記事(公式なのに分かりやすかった):
コメント