RLS
RLS(Row Level Security = 行レベルのアクセス制御)は、データベース層でデータ混濁問題を回避する技術。
SQL実行時の権限によって、SELECTの結果にアクセス権限のある行だけが返されるという素敵な仕組み。
これによって、フレームワークやライブラリがWHERE句を注入するアプリケーション層でのデータ分離方式よりも、シンプルに問題を解決できる。
RLSの多くの例は、DBコネクションのユーザーでアクセス制御を行っているが、PostgreSQLのROLEを使うことで、DBコネクション確立後に SET ROLE="2" のようにして任意のID(例えばテナントID)でアクセス制御を行える。
Using Postgres Row-Level Security in Python and Djangoより
https://scrapbox.io/files/60835823247345001c3dbccc.png
RDBMSの対応状況(2021/04/24時点)
SQL Server 2016 (13.x) 以降
行レベルのセキュリティ - SQL Server | Microsoft Docs
PostgreSQL 9.5 (2016) 以降
PostgreSQL: Documentation: 9.5: Row Security Policies
設定方法などのサンプルコード付き
MySQL 5.6.51 は非対応
Djangoでのコンセプト実装
Django + RLS でマルチテナント を参照
RLSの設定方法
2020/10/31 マルチテナントアーキテクチャのアプリをより安全にするために PostgreSQL の行レベルセキュリティ (行セキュリティポリシー、 Row Level Security) をすぐに体験できるようにした記録 – oki2a24
すぐにお試しの環境を作れるように記録
2020/08/13 Using Postgres Row-Level Security in Python and Django
英語だけどシンプルで分かりやすい
DjangoでRLSを行う場合の実装方法、注意点も書いてある
これを元に、コンセプト実装を作った https://github.com/shimizukawa/django-pg-rls
実際に、DjangoのDBにRLSを設定したログ(Djangoでの画面はこちら: Django + RLS でマルチテナント)
code:console
$ docker-compose exec db psql db -U db
psql (13.2 (Debian 13.2-1.pgdg100+1))
Type "help" for help.
db=# \dt
public | auth_group | table | db
public | auth_group_permissions | table | db
public | auth_permission | table | db
public | customers | table | db
public | django_admin_log | table | db
public | django_content_type | table | db
public | django_migrations | table | db
public | django_session | table | db
public | tenants | table | db
public | tenant_users | table | db
public | tenant_users_groups | table | db
public | tenant_users_user_permissions | table | db
db=# select * from customers;
1 | aa | aa | aa | aa | 2
2 | bb | bb | bb | bb | 1
3 | cc | cc | cc | cc | 2
4 | dd | dd | dd | dd | 1
5 | ee | ee | ee | ee | 2
6 | ff | ff | ff | ff | 1
7 | gg | gg | gg | gg | 1
8 | hh | hh | hh | hh | 2
9 | ii | ii | ii | ii | 1
10 | jj | jj | jj | jj | 2
db=# CREATE ROLE "1";
CREATE ROLE
db=# CREATE ROLE "2";
CREATE ROLE
db=# CREATE ROLE tenantuser;
CREATE ROLE
db=# GRANT select, insert ON customers TO tenantuser;
GRANT
db=# GRANT tenantuser TO "1";
GRANT ROLE
db=# GRANT tenantuser TO "2";
GRANT ROLE
db=# ALTER TABLE customers ENABLE ROW LEVEL SECURITY;
ALTER TABLE
db=# CREATE POLICY tenantuser_customers ON customers USING(tenant_id::text = current_user);
CREATE POLICY
db=# SELECT session_user, current_user;
db | db
db=# SELECT * FROM customers;
1 | aa | aa | aa | aa | 2
2 | bb | bb | bb | bb | 1
3 | cc | cc | cc | cc | 2
4 | dd | dd | dd | dd | 1
5 | ee | ee | ee | ee | 2
6 | ff | ff | ff | ff | 1
7 | gg | gg | gg | gg | 1
8 | hh | hh | hh | hh | 2
9 | ii | ii | ii | ii | 1
10 | jj | jj | jj | jj | 2
db=# SET ROLE "1";
SET
db=> SELECT * FROM customers;
2 | bb | bb | bb | bb | 1
4 | dd | dd | dd | dd | 1
6 | ff | ff | ff | ff | 1
7 | gg | gg | gg | gg | 1
9 | ii | ii | ii | ii | 1
参考文献
2019/06/24: PostgreSQLのRow Level Securityを使ってマルチテナントデータを安全に扱う - HRBrain Blog
HRBrain社での事例、日本語で詳しく説明してくれている
RLSを使うことで多少パフォーマンスに影響がでる可能性があるようです。
パフォーマンス問題については、Postgres+Citusで回避するのがよさそう
2019/08/31: Row Level Securityはマルチテナントの銀の弾丸になりうるのか / Row Level Security is silver bullet for multitenancy? - Speaker Deck
HRBrain社での事例のプレゼン
2020/06/01: PostgreSQL の行レベルのセキュリティを備えたマルチテナントデータの分離 | Amazon Web Services ブログ
2021/05/23: PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgresSQLの行レベルセキュリティと、SpringAOPによる処理を組み合わせて、ログインしているテナントのデータにしかアクセスできなくする仕組みを実現