Row-Level Security (RLS) - політики на рівні бази, що визначають, які рядки таблиці бачить і може змінювати роль. Фільтр додається до кожного запиту автоматично - навіть якщо в коді його забули.
Ізоляція тенантів:
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects FORCE ROW LEVEL SECURITY; -- діє й на власника таблиці
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);
USING- які рядки видно (SELECT,UPDATE,DELETE).WITH CHECK- які рядки дозволено записати (INSERT,UPDATE): не можна вставити рядок з чужимtenant_id.
Застосунок на початку кожного запиту чи транзакції встановлює тенанта:
BEGIN;
SET LOCAL app.tenant_id = '42';
SELECT * FROM projects; -- лише проєкти тенанта 42, без WHERE у коді
COMMIT;
Чому це цінно: у мультитенантному застосунку найнебезпечніший баг - запит без фільтра за тенантом. З RLS такий запит поверне не чужі дані, а лише дані поточного тенанта (або нічого, якщо тенант не встановлений).
Підводні камені:
- Суперкористувачі й ролі з
BYPASSRLSполітики ігнорують, а власник таблиці - теж, безFORCE ROW LEVEL SECURITY. Застосунок має працювати від звичайної ролі. - Пул з'єднань: з PgBouncer у режимі transaction pooling - лише
SET LOCALусередині транзакції.SETбезLOCALзалишить тенанта в з'єднанні, яке потім отримає інший запит - найгірший можливий сценарій. - Продуктивність: умова політики додається до кожного запиту - потрібен індекс на
tenant_id, а функції в політиці мають бути простими. - Фонові задачі й адмінка мають явно працювати або в контексті тенанта, або від окремої ролі з обґрунтованим обходом.
- Налагодження складніше: «чому запит нічого не повертає» часто означає, що не встановлено змінну тенанта.
RLS - сильний додатковий рівень захисту, але не заміна перевіркам у застосунку.