8. Multi-Tenant Magic: Under the Covers of the Force.com data
Architecture より
8
シングルテナントアーキテクチャ
ユーザー企業ごとにデータベースを用
意すると、データベースごとに管理者
が必要となるため多くのシステム管理
者が必要となり、またインフラの利用
効率をあげることも難しい。
マルチテナントアーキテクチャ
全ユーザーが1つのインフラを共有する
ため管理者が少なくて済み、インフラ
の稼働効率も高くなる
24. db=# CREATE TABLE rls (id SERIAL PRIMARY KEY, tenant_id INTEGER);
db=> INSERT INTO rls (tenant_id) VALUES (1),(2),(3),(1),(2),(3);
db=> SELECT * FROM rls;
id | tenant_id
----+-----------
1 | 1
2 | 2
...
6 | 3
(6 rows)
実験用テーブル rls を作る
24
rls テーブル
id tenant_id
1 1
2 2
3 3
4 1
5 2
6 3
25. db=# CREATE ROLE “1”; -- tenant_id=1 の行のみアクセスできるROLE(予定)
db=# SET ROLE “1”;
db=> SELECT * FROM rls;
ERROR: permission denied for table customers
db=# RESET ROLE; -- 接続ユーザー権限に戻る
新規のロール "1" を作る
25
rls テーブル
id tenant_id
1 1
2 2
3 3
4 1
5 2
6 3
Postgres users
“db” User
“1” Role
OK
NG
■User ■Role ■RLS
https://www.postgresql.org/docs/13/sql-set-role.html
USER でも ROLE でも可能ですが、ROLE で制御する
理由は29スライド付近で説明します。
PostgreSQLのロール - Qiita
26. db=# GRANT ALL ON ALL TABLES IN SCHEMA public TO ”1”;
db=# GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO ”1”;
db=# SET ROLE “1”;
db=# SELECT * FROM rls;
1 | 1
2 | 2
...
6 | 3
(6 rows)
db=# RESET ROLE; -- 接続ユーザー権限に戻る
"1" に全権限を付ける
26
全権限があるので、全行見えてます rls テーブル
id tenant_id
1 1
2 2
3 3
4 1
5 2
6 3
Postgres users
“db” User
“1” Role
OK
OK
■User ■Role ■RLS
https://www.postgresql.org/docs/13/sql-grant.html
27. db=# ALTER TABLE rls ENABLE ROW LEVEL SECURITY;
db=# SET ROLE “1”;
db=# SELECT * FROM rls;
id | tenant_id
----+-----------
(0 rows)
db=# RESET ROLE; -- 接続ユーザー権限に戻る
rls テーブルのRLSを有効化
27
SELECT権限はありますが、
RLSによって制限されました
rls テーブル
id tenant_id
1 1
2 2
3 3
4 1
5 2
6 3
Postgres users
“db” User
“1” Role
OK
OKだが
1行も見
えない
■User ■Role ■RLS
https://www.postgresql.org/docs/13/ddl-rowsecurity.html
28. db=# CREATE POLICY foo ON rls USING(tenant_id::text = current_user);
db=# SET ROLE “1”;
db=# SELECT * FROM rls;
id | tenant_id
----+-----------
1 | 1
4 | 1
(2 rows)
rls テーブルのRLSポリシーを設定
28
rls テーブル
id tenant_id
1 1
2 2
3 3
4 1
5 2
6 3
Postgres users
“db” User
“1” Role
SELECT権限がありますが、
RLSポリシーによって
対象行が制限されました
OK
OK
■User ■Role ■RLS
https://www.postgresql.org/docs/13/ddl-rowsecurity.html
30. tenant=2 で
ログイン
2
dbで接続
セッションでのSELECT
30
全行
■User ■Role ■RLS
customers テーブル
id name tenant_id
1 customer_1 2
2 customer_2 2
3 customer_3 3
4 customer_4 1
5 customer_5 3
6 customer_6 1
7 customer_7 1
8 customer_8 2
Postgres users
“db” User
“1” User
“2” User
“3” User
SELECT * FROM customers;
31. customers テーブル
id name tenant_id
1 customer_1 2
2 customer_2 2
3 customer_3 3
4 customer_4 1
5 customer_5 3
6 customer_6 1
7 customer_7 1
8 customer_8 2
tenant=2 で
ログイン
2
Postgres users
“db” User
“1” User
“2” User
“3” User
セッション+RLSでのSELECT
31
CREATE POLICY foo ON customers USING(tenant_id::text = session_user);
接続Userで制御
■User ■Role ■RLS
SELECT * FROM customers;
tenant_id =2 の全行
“2” で接続
ログインユーザー毎に、
DjangoからDBへ接続する
ユーザーを切り替えるのは難しい
32. tenant=2 で
ログイン
2
tenant_id =2 の全行
Postgres users
“db” User
“1” Role
“2” Role
“3” Role
dbで接続
SET ROLE “2”;
SELECT * FROM customers;
RLS+Roleを介したSELECT(Role密結合)
32
CREATE POLICY foo ON customers USING(tenant_id::text = current_user);
CREATE ROLE “1”;
GRANT ALL ON TABLES ... TO “1”;
GRANT ALL ON SCHEMAS ... TO “1”;
-- 2, 3, 以下繰り返し
customers テーブル
id name tenant_id
1 customer_1 2
2 customer_2 2
3 customer_3 3
4 customer_4 1
5 customer_5 3
6 customer_6 1
7 customer_7 1
8 customer_8 2
Roleで制御
■User ■Role ■RLS
ログインユーザーのHTTPリクエスト毎に、
Roleを切り替えるMiddlewareを用意
DjangoからDBへの接続は、
常に1つのuser/passwordを使う
33. customers テーブル
id name tenant_id
1 customer_1 2
2 customer_2 2
3 customer_3 3
4 customer_4 1
5 customer_5 3
6 customer_6 1
7 customer_7 1
8 customer_8 2
tenant=2 で
ログイン
2
Postgres users
“db” User
“tenantuser” Role
“1” Role
“2” Role
“3” Role
dbで接続
SET ROLE “2”;
SELECT * FROM customers;
RLS+Roleを介したSELECT(Role疎結合)
33
CREATE POLICY foo ON customers USING(tenant_id::text = current_user);
CREATE ROLE “tenantuser”;
GRANT ALL ON TABLES … TO “tenantuser”;
GRANT ALL ON SCHEMAS … TO “tenantuser”;
GRANT “tenantuser” TO "1";
-- 以下繰り返し
Roleで制御
■User ■Role ■RLS
テナントのRoleは、代理ロール ”tenantuser” 経由でテー
ブルへのアクセスが許可される
tenant_id =2 の全行
35. 1 & 2. 初期化
35
db=# CREATE ROLE “tenantuser”;
db=# GRANT ALL ON ALL TABLES IN SCHEMA public TO ”tenantuser”;
db=# GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO ”tenantuser”;
※ 初期化は、後から追加されたテーブルにも個別適用が必要
■User ■Role ■RLS
-- for <table> in <tables_to_enable_rls>:
db=# ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;
db=# CREATE POLICY <name> ON <table> USING(tenant_id::text = current_user);
1. 代理ROLEを作成し、全テーブルのGRANT ALLを設定
2. テナント制御するテーブルにRLS有効化&ポリシー設定
36. 3. CREATE ROLE -- テナント追加時
36
■User ■Role ■RLS
from django.db.models.signals import post_save
def on_create_tenant(sender, instance, created, **kwargs):
if created: # レコード追加時
tenant_id = instance.tenant_id
with connection.cursor() as cursor:
cursor.execute(f'CREATE ROLE "{tenant_id}"')
cursor.execute(f'GRANT "tenantuser" TO "{tenant_id}"')
# DBにレコードが追加された後に実行する
post_save.connect(on_create_tenant, sender=models.Tenant)
https://docs.djangoproject.com/ja/3.2/topics/signals/
37. 4. SET ROLE -- Middlewareでテナント判別
37
■User ■Role ■RLS
https://docs.djangoproject.com/ja/3.2/topics/http/middleware/
class RlsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tenant_id = getattr(request.user, 'tenant_id', None)
if tenant_id: # テナントに所属しないスーパー管理者はROLE設定不要
with connection.cursor() as cursor:
cursor.execute(f'SET ROLE "{tenant_id}" ')
response = self.get_response(request)
return response
※ Middlewareを通らない処理では個別に SET ROLE が必要
38. 4. SET ROLE -- Middleware以外でもテナント設定
38
■User ■Role ■RLS
https://django-tenants.readthedocs.io/en/latest/use.html#tenant_context
@contextmanager
def tenant_context(tenant_id: int):
with connection.cursor() as cursor:
cursor.execute(f'SET ROLE "{tenant_id}" ')
Yield
cursor.execute(f'RESET ROLE')
# バッチや非同期呼び出し等、Middlewareを経由しない場合の呼び出し
def some_command(tenant_id):
with tenant_context(tenant_id): # SET ROLE される
objs = DjangoModel.objects.all()