Row Level Security (RLS) este una dintre cele mai puternice funcționalități de securitate din PostgreSQL. Spre deosebire de permisiunile clasice GRANT care operează la nivel de tabel întreg sau coloană, RLS permite definirea unor politici de securitate la nivel de rând individual. Astfel, aceeași instrucțiune SELECT * FROM facturi va returna pentru Utilizatorul A doar facturile companiei sale, iar pentru Utilizatorul B doar facturile companiei lui, direct din motorul bazei de date!
Implementarea izolării Multi-Tenant cu RLS
-- 1. Creăm tabelul partajat pentru toți clienții (tenants)
CREATE TABLE facturi (
id SERIAL PRIMARY KEY,
tenant_id INT NOT NULL,
numar_factura VARCHAR(50) NOT NULL,
total NUMERIC(10, 2) NOT NULL,
client VARCHAR(100) NOT NULL
);
-- 2. Activăm securitatea la nivel de rând pe tabel
ALTER TABLE facturi ENABLE ROW LEVEL SECURITY;
-- 3. Definim politica declarativă bazată pe o variabilă de sesiune a aplicației:
CREATE POLICY facturi_tenant_isolation ON facturi
FOR ALL
TO application_user
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::INT)
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::INT);
Clauza USING vs. Clauza WITH CHECK
- USING: Se aplică la citiri (
SELECT) și la selectarea rândurilor care urmează să fie actualizate sau șterse (UPDATE,DELETE). Rândurile pentru care expresia returneazăfalsedevin complet invizibile (nu returnează eroare, ci pur și simplu nu sunt returnate). - WITH CHECK: Se aplică la scrieri (
INSERTși noile valori dinUPDATE). Previne situațiile în care un chiriaș (tenant) încearcă să insereze un rând cutenant_id-ul altei companii sau să mute o factură existentă către alt tenant. Dacă expresia e falsă, PostgreSQL aruncă o eroare explicită de violare de politică.
Cum setează aplicația web contextul de sesiune
-- Înainte de a rula interogările utilizatorului autentificat, aplicația setează variabila locală:
BEGIN;
SET LOCAL app.current_tenant_id = '42';
-- Utilizatorul vede doar facturile cu tenant_id = 42:
SELECT * FROM facturi;
-- O încercare malițioasă de a citi datele altei companii returnează 0 rânduri:
SELECT * FROM facturi WHERE tenant_id = 99; -- 0 rows returned!
COMMIT;
Reguli Critice de Producție pentru RLS
- Superuserii sar peste RLS: În mod implicit, utilizatorul
postgres(sau orice utilizator cu atributulSUPERUSER) ocolește complet politicile RLS. Asigurați-vă că aplicația se conectează cu un rol non-superuser! - FORCE ROW LEVEL SECURITY: Dacă doriți ca proprietarul tabelului (table owner) să fie de asemenea supus politicilor RLS, executați:
ALTER TABLE facturi FORCE ROW LEVEL SECURITY;. - Indecși pe coloana de politică: Creați întotdeauna un index pe coloana folosită în politică (ex:
CREATE INDEX idx_facturi_tenant ON facturi(tenant_id);) pentru a evita scanările secvențiale la fiecare interogare.