マルチテナントとは?RLS(Row Level Security)との関係をわかりやすく

自分用メモ。

調査せずに雑に書いてるので、テキトーに見てください。

最後らへんは、実機に試さずに公式ドキュメントを見て「多分こうだろうな」という感じで書いたので、かなり間違ってる可能性があります

マルチテナントとは?

ざっくりいうと、「複数の会社の情報がDBに入ってるよ~」なこと。

マルチテナントアーキテクチャとは?

「複数の会社の情報がDBに入ってるよ~」な状態のとき、

「こうやって複数の会社の情報を管理すれば良い感じなんじゃね?」な設計のこと。

 

たとえば、👇などがあるらしい。

  1. 「会社ごとにサーバーを分けちゃおうぜ」な方法
  2. 「会社ごとにDBを分けちゃおうぜ」な方法
  3. 「会社ごとにテーブルを分けちゃおうぜ」な方法
  4. 「全部の会社を1つのテーブルで管理しようぜ」な方法

 

たとえば、会社Aと会社Bがある場合、それぞれ👇みたいな感じになります。

  1. 「会社ごとにサーバー自体を分けちゃおうぜ」な方法
    • 会社A用のサーバーを建てて、その中でPostgreSQLのようなDBを動かしたり。
    • 会社B用のサーバーを建てて、その中でPostgreSQLのようなDBを動かしたり。
  2. 「会社ごとにDBを分けちゃおうぜ」な方法
    • 会社A用のDBと、会社B用のDBをつくって、それぞれにAccountテーブルみたいなテーブルが存在する
  3. 「会社ごとにテーブルを分けちゃおうぜ」な方法
    • DBは1つだけど、会社A用のAccountテーブル、会社B用のAccountテーブルがある・・・みたいな感じ。
  4. 「全部の会社を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ですね。

 

おわり

 

参考にした記事(公式なのに分かりやすかった):

5.7. 行セキュリティポリシー

CREATE POLICY

SQL
スポンサーリンク
この記事を書いた人
penpen

1991生まれ。WEBエンジニア。

技術スタック:TypeScript/Next.js/Express/Docker/AWS

フォローする
フォローする

コメント

タイトルとURLをコピーしました