Azure Database for PostgreSQL と Azure Cosmos DB for PostgreSQL で RLS (Row-Level Security) を使ってみた

2024.01.03

いわさです。

マルチテナント SaaS のアーキテクチャーを設計する際にはデータベースのテナント分離をどのように実装するかについても考える必要があります。

データベースに RDBMS を選択した時に、同一のデータベースサーバーを複数テナントで共用する場合があります。
スキーマ単位でテナントを分ける場合もあれば、同一テーブル内にテナントを識別出来る列を設ける場合もあります。

後者の場合はアプリケーション側からクエリの条件としてテナント識別情報を指定するパターンがありますが、追加のセキュリティ対策として RLS (Row Level Security) という機能を使える場合があります。

上記の Azure アーキテクチャセンターにて次の記述を見つけました。

This feature is available in Azure SQL and Postgres Flex, but it's not available in other databases, like MySQL or Azure Cosmos DB.

MySQL に RLS の仕組みがないのはよく知られていますが、Azure Cosmos DB でも使えないという記述が。
Ignite 2022 で Azure Cosmos DB for PostgreSQL が使えるようになりましたが、こちらでも使えないということなのでしょうか。

本日は Azure Database for PostgreSQL と Azure Cosmos DB for PostgreSQL で RLS の設定と確認を行ってみました。

Azure Database for PostgreSQL

まずは Azure Database for PostgreSQL で試してみます。

次のような最小限の構成でサーバーを用意します。

Row Security Policy の PostgreSQL 公式ドキュメントを参考にしつつ、適当なテーブルやレコード、ロールを作成して確認してみましょう。

まずテーブルを作成し、Row-Level Security を有効化します。
ポリシーはカレントユーザーとテナント列をマッピングさせます。

postgres=> CREATE TABLE tenanthoge (tenant text, hoge1 text, hoge2 text);
CREATE TABLE
postgres=> ALTER TABLE tenanthoge ENABLE ROW LEVEL SECURITY;
ALTER TABLE
postgres=> CREATE POLICY tenant_policy ON tenanthoge USING (tenant = current_user);
CREATE POLICY

3 テナント分のテナント用レコードを用意しました。
管理ユーザーからは参照が出来る状態です。

postgres=> INSERT INTO tenanthoge VALUES ('tenant1', 'aaa', '111');
INSERT INTO tenanthoge VALUES ('tenant1', 'aaa', '222'); 
INSERT INTO tenanthoge VALUES ('tenant2', 'bbb', '333'); 
INSERT INTO tenanthoge VALUES ('tenant3', 'ccc', '444');
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1

postgres=> select * From tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant1 | aaa   | 111
 tenant1 | aaa   | 222
 tenant2 | bbb   | 333
 tenant3 | ccc   | 444
(4 rows)

テナント用のロールを作成し、テーブルへの SELECT の権限を与えます。
また、今回はpostgresから各テナントロールへのスイッチを行って動作確認しようと思っているので、ロールメンバーの資格も与えておきます。

postgres=> CREATE ROLE tenant1 with login password 'password';
CREATE ROLE
postgres=> CREATE ROLE tenant2 with login password 'password';
CREATE ROLE
postgres=> CREATE ROLE tenant3 with login password 'password';
CREATE ROLE
postgres=> GRANT SELECT ON tenanthoge TO tenant1, tenant2, tenant3;
GRANT

postgres=> GRANT tenant1 to postgres;
GRANT ROLE
postgres=> GRANT tenant2 to postgres;
GRANT ROLE
postgres=> GRANT tenant3 to postgres;
GRANT ROLE

SET ROLEを使ってカレントセッションのロールを設定します。RESET ROLEで元に戻しています。

postgres=> SET ROLE tenant1;
SET
postgres=> select * from tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant1 | aaa   | 111
 tenant1 | aaa   | 222
(2 rows)

postgres=> RESET ROLE;
RESET
postgres=> SET ROLE tenant2;
SET
postgres=> select * from tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant2 | bbb   | 333
(1 row)

postgres=> RESET ROLE;
RESET
postgres=> SET ROLE tenant3;
SET
postgres=> select * from tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant3 | ccc   | 444
(1 row)

期待どおり、指定したロールで Row-Level Security が動作していますね。

Azure Cosmos DB for PostgreSQL

続いて、 Azure Cosmos DB for PostgreSQL で試してみましょう。

API オプションで Azure Cosmos DB for PostgreSQL を選択します。

AWS 界隈の方にはよく「Azure Cosmos DB って Amazon DynamoDB みたいなやつでしょ?」と言われるのですがちょっと違っていて、Cosmos DB では NoSQL 以外にも RDB やグラフ DB などなど多様なインターフェースで実はアクセスすることが出来ます。

PostgreSQL インターフェースは Microsoft Ignite 2022 で GA となった機能で、内部的には Citus という PostgreSQL で分散データベースを実現出来る拡張機能が使われています。

と言いつつ私も実は GA 後に触っていなくて初めて触ります。
構成画面で適当に構成を選択すると料金が算出されました。

vCore ベースでノードがプロビジョニングされている時間に応じた従量課金となっているようです。
料金ページはこちらです。

リソース作成後に psql でアクセスし、Azure Database for PostgreSQL と同じ流れで検証してみたいと思います。

接続してテーブルやレコード、ロールを作成してみましょう。

psql -h c-hoge0103cosmos.ki4envhvginnux.postgres.cosmos.azure.com -U citus    
Password for user citus: 
psql (14.9 (Homebrew), server 15.5 (Ubuntu 15.5-1.pgdg20.04+1))
WARNING: psql major version 14, server major version 15.
         Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

citus=> CREATE ROLE tenant1 with login password 'password';
NOTICE:  not propagating CREATE ROLE/USER commands to worker nodes
HINT:  Connect to worker nodes directly to manually create all necessary users and roles.
ERROR:  permission denied to create role

テーブルやレコードは作成出来たのですが、ロールの作成に失敗しました。
次のドキュメントによると Azure ポータルや Azure API から作成し、コーディネーターノードに作成されたロールがワーカーノードに伝播されるようです。

ポータルからロールを追加

今回は次のように Authentication メニューからテナント用のロールを作成しました。

その後 psql で再び作成されたロールなどを確認してみました。

citus=> \du
                                                   List of roles
   Role name   |                         Attributes                         |              Member of               
---------------+------------------------------------------------------------+--------------------------------------
 azure_ad_user | Cannot login                                               | {}
 citus         |                                                            | {pg_monitor,tenant1,tenant2,tenant3}
 postgres      | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 tenant1       |                                                            | {}
 tenant2       |                                                            | {}
 tenant3       |                                                            | {}

作成されていそうですね。
ロールへの権限は与えますが、管理用のcitusロールはポータルから作成したロールのメンバーになっているようなので、そのままスイッチ出来そうです。

citus=> SET ROLE tenant1;
SET
citus=> select * From tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant1 | aaa   | 111
 tenant1 | aaa   | 222
(2 rows)

citus=> RESET ROLE;
RESET
citus=> SET ROLE tenant2;
SET
citus=> select * from tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant2 | bbb   | 333
(1 row)

citus=> RESET ROLE;
RESET
citus=> SET ROLE tenant3;
SET
citus=> select * from tenanthoge;
 tenant  | hoge1 | hoge2 
---------+-------+-------
 tenant3 | ccc   | 444
(1 row)

ロールを切り替えてテーブルへアクセスしてみたところ、対象テナントのレコードのみアクセスすることが出来ました。

Row-Level Security 使えていますね。使えないはずだが使えている状態なのだろうか。
よくよく調べてみると Cosmos DB 公式ドキュメントには Row-Level Security のコンテンツが用意されていました。冒頭の Azure アーキテクチャセンターとの記述の整合性が取れていないような気もしますが、Cosmod DB の仕様としては使えると考えて良さそうです。

さいごに

本日は Azure Database for PostgreSQL と Azure Cosmos DB for PostgreSQL で RLS (Row-Level Security) を使ってみました。

Azure Cosmos DB for PostgreSQL でも問題なく RLS 使えそうでしたね。
そして、Cosmos DB for PostgreSQL を初めて使いました。
RU ではなくてプロビジョニングされたノードベースでの料金になるためか、資産された料金が思ったより高かったです。シャーディングを意識した設定を行ってスケーリングの検証まで出来ると有効性を実感出来るのかもしれないので、また検証したいところです。