Multitenancy sa RLS i tvrda granica Control Plane-a
Izolacija između klijenata je rešen problem; izolacija između platformskog osoblja i podataka klijenta skoro nikad nije — a gradi se istim alatom i jednim testom.
- Objavljeno
- Autor
- Konis Software
Kupac SaaS ERP-a najčešće prekasno postavi jedno pitanje: ko iz firme koja pravi taj softver može, ako to poželi u utorak popodne, da otvori našu glavnu knjigu, listu kupaca sa uslovima plaćanja i PPP-PD prijavu sa neto zaradom svakog zaposlenog. Odgovor da postoji potpisan ugovor o poverljivosti nije arhitektura. To je nada sa pravnim zaglavljem.
Dve granice se pri tome stalno mešaju. Prva je izolacija između klijenata: tenant A ne sme da vidi podatke tenanta B. Ona je tehnički poznata i uglavnom rešena. Druga je izolacija između platformskog osoblja i podataka klijenta. Nju rešava mnogo manje sistema — ne zato što je teška, nego zato što je neprijatna: neko mora sam sebi da postavi zid.
Tri načina da se izoluje tenant
Baza po tenantu, šema po tenantu ili deljena baza sa Row Level Security. Izbor deluje kao stvar ukusa sve do prve promene šeme nad tristo klijenata, ili do prvog upita koji je neko napisao bez filtera po tenantu.
| Model | Izolacija | Promena šeme | Gde puca |
|---|---|---|---|
| Baza po tenantu | Najjača, fizička | N migracija, N ishoda | Bekap, monitoring i konekcije rastu linearno; verzije se razilaze |
| Šema po tenantu | Srednja, logička | Jedna migracija × N šema | search_path je runtime stanje: izolacija zavisi od toga ko ga postavlja |
| Deljena baza + RLS | Logička, u bazi | Jedna migracija, jedan ishod | Zavisi od politike na SVAKOJ tabeli i od discipline oko konteksta |
Deljena baza sa RLS je jedina od tri opcije u kojoj propust u kodu ne završava kao curenje: upit bez uslova po tenantu vraća redove tekućeg tenanta, a ne sve, jer odluka o vidljivosti živi u bazi, ne u aplikaciji. Cena je što se granica seli tamo gde se lako zaboravi — u definiciju svake tabele.
RLS koji ne zavisi od discipline programera
RLS je koristan samo ako ga je nemoguće zaobići omaškom. O tome odlučuju tri detalja, i sva tri se redovno preskoče.
- ENABLE nije dovoljno, treba i FORCE. Bez FORCE ROW LEVEL SECURITY politika ne važi za vlasnika tabele, a vlasnik je po pravilu rola pod kojom radi alat za migracije. Prva seed skripta puštena pod tom rolom prolazi kroz politiku kao da je nema.
- Runtime rola bez BYPASSRLS. Aplikacija radi pod običnom rolom sa grantovima po tabeli. Ako igde postoji superuser konekcija za pozadinske poslove, RLS je ukras.
- Politika koja greši u pravu stranu. USING (tenant_id = current_setting('app.tenant_id')::uuid) izgleda ispravno dok se ne izvrši bez konteksta: tada baca grešku umesto da vrati prazan skup. Oblik sa current_setting('app.tenant_id', true) vraća NULL, poređenje daje NULL, rezultat je nula redova. Fail-closed.
Kontekst koji umire sa transakcijom
Vrednost app.tenant_id mora da stigne od potvrđenog tokena do konekcije i da nestane pre nego što se konekcija vrati u pool. Sesijska varijanta, sa RESET pri vraćanju, radi tačno dok se niko ne zaboravi: jedan izuzetak na pogrešnom mestu i sledeći zahtev čita tuđe podatke sa iste konekcije. Transakciona nema tu klasu grešaka — SET LOCAL na početku transakcije, a brišu ga i commit i rollback. Kod koji je zaboravio da otvori transakciju dobija nula redova umesto tuđih.
Politika koja fali na jednoj od četiristo tabela
Izolacija nije osobina sistema nego osobina svake pojedinačne tabele, a tabela ima nekoliko stotina. Šansa da pregled koda uhvati tabelu bez politike u pull requestu sa dvanaest fajlova je mala, a cena promašaja je potpuna. Jedini mehanizam koji tu radi je meta-test: čita katalog baze, nađe svaku tabelu sa kolonom tenant_id i obori build ako nema ENABLE, FORCE i politiku očekivanog oblika. Mora da poredi izraz politike, ne ime: tačno ime nad pogrešnim izrazom prolazi pregled. Uz test ide eksplicitna lista izuzetaka: šifarnik država, Incoterms, registar tenanta.
Pravilo koje nije provereno testom nije pravilo nego preporuka. Izolacija tenanta ne trpi preporuke.
Druga granica: Control Plane i Data Plane
RLS uređuje odnos klijent–klijent i ne dodiruje odnos platforma–klijent. Platformsko osoblje ima legitimne poslove: otvaranje tenanta, pretplate i licence, kvote i potrošnju, verziju šeme, status bekapa i restore probe, p95 latenciju, incidente. Nijedan ne traži da neko pročita stavku fakture, izvod sa modelom 97 ni red POPDV evidencije. Zato podaci platforme i podaci klijenata ne žive u istom prostoru ni pod istim identitetom.
| Pitanje | Control Plane | Tenant Data Plane |
|---|---|---|
| Šta sadrži | Tenanti, paketi, cenovnici, pretplate, licence, potrošnja, zdravlje, platformski audit | Poslovni podaci klijenta: dokumenti, partneri, glavna knjiga, zalihe, zarade |
| Šema i DB rola | Odvojena šema bez tenant RLS, sopstvena rola | Tenant tabele sa FORCE RLS, runtime rola bez BYPASSRLS |
| Ko pristupa | Platformsko osoblje, poseban realm, obavezan MFA | Korisnici tenanta |
| Pristup drugoj strani | Nikad podrazumevano, samo kroz support access grant | Nikad |
| Interfejs | Poseban portal, van ERP-a | ERP |
Poslednji red te tabele nije stvar politike nego grantova. Runtime rola aplikacije nema nijedan grant na šemu Control Plane-a; rola Control Plane-a nema nijedan na tenant tabele. Granica koja postoji kao odsustvo granta preživljava refaktorisanje, novog kolegu i petak popodne; provera isPlatformStaff() u servisu ne preživljava. Zato Control Plane ne čita poslovne tabele ni radi statistike: potrošnja se meri događajima koje moduli emituju, a dimenzije koje bi otkrile sadržaj idu heširane. Platforma zna da je tenant napravio 14.000 dokumenata u martu. Ne zna nijedan od njih.
Zašto Super Tenant nije rešenje nego rizik
Najčešći odgovor na potrebu podrške je super tenant: nalog koji je tehnički tenant, ali vidi sve. Isti interfejs, ista šema, jedan flag u tokenu. Na dijagramu deluje elegantno; u radu je pogrešan iz četiri razloga.
- Traži rupu u politici. Nalog koji vidi sve mora nekako da prođe kroz RLS: ili dobija BYPASSRLS, ili politika dobija granu sa proverom dodatnog parametra. Od tog trenutka izolacija svih klijenata visi o jednoj proveri role.
- Tiho se širi. Svaka nova tabela nasledi šablon politike zajedno sa granom izuzetka. Izuzetak napravljen jednom postaje podrazumevano ponašanje sistema, a posle godinu dana ga niko ne čita kao izuzetak.
- Ubija audit. Kad je pristup podrazumevan, audit snima svakodnevni rad. Zapis koji se javlja četrdeset hiljada puta mesečno ne odgovara ni na jedno pitanje. Audit vredi samo kad snima izuzetak.
- Nema odgovor za klijenta. Na pitanje ko je od vas gledao naše zarade u martu, super tenant arhitektura ume da odgovori samo: svi koji su smeli, a da li jesu — ne znamo pouzdano.
Support access: pristup kao događaj, ne kao stanje
- 1
Dijagnostika pre pristupa
Prvi potez nije zahtev za podacima nego dijagnostički paket: verzija aplikacije i šeme, poslednja migracija, konfiguracija tipa dokumenta, log odluke automatike, stack trace. Bez ličnih podataka i bez iznosa. Većina prijava se zatvori tu.
- 2
Zahtev sa obimom i rokom
Ako paket nije dovoljan, inženjer traži pristup i mora da napiše šta traži: koji tenant, koji razlog i broj incidenta, koji obim (read-only, koji moduli) i koliko dugo, najviše osam sati. Zahtev bez roka nije zahtev.
- 3
Odobrenje u klijentovom sistemu
Administrator klijenta odobrava zahtev u svom ERP-u, ne mejlom i ne telefonom. Mejl nije kontrola pristupa: ne postoji u sistemu koji odluku sprovodi i ne može se opozvati jednim klikom.
- 4
Rad pod svedokom
Grant izdaje token sa tačno tim obimom. Sesija nosi trajnu oznaku u interfejsu. Svaka radnja upisuje se u dva odvojena audita: platformski i tenantov. Tenantov je važniji — klijent ne sme da zavisi od naše kopije zapisa.
- 5
Automatski istek i izveštaj
Grant ističe sam, bez ijedne ljudske radnje. Klijent dobija izveštaj sesije: ko, kada, koliko dugo, koji ekrani, koji zapisi. Pristup koji se gasi ručno se ne gasi.
Na konkretnoj prijavi: rezervacija po FEFO pravilu bira LOT sa kasnijim rokom umesto ranijeg. Inženjeru trebaju redosled LOT-ova sa rokovima, pravilo rezervacije i log odluke — ne ime kupca, ne cena, ne ostatak porudžbine. Zato dijagnostički paket ide prvi, a grant, ako uopšte zatreba, ostaje read-only i na jednom modulu. Hitan slučaj — proizvodnja stoji u dva ujutru — mora da postoji, ali sa unapred ugovorenom cenom: drugi par očiju, trenutna notifikacija, potvrda posle. Izuzetak koji nema cenu postane redovan put za tri meseca.
Šta ovo košta
- Indeksi. Svaki složeni indeks počinje kolonom tenant_id, jer je ona predikat na svakom upitu. Indeks koji to ne poštuje planer koristi lošije nego što se očekuje, a vidi se tek na tenantu sa milion redova.
- Predikat na svakom planu. Politika je dodatni uslov u svakom upitu. Trošak je mali, ali nije nula i najviše se oseti na upitima koji vade malo redova iz velike tabele.
- Zbunjujuć razvoj. Kod koji radi van transakcije ne dobija grešku nego prazan rezultat. To je tačno ono što se hoće u produkciji i tačno ono što pojede pola sata u razvoju: fail-closed greši u pravu stranu, ali greši tiho.
- Batch preko više tenanata. Posao koji obrađuje više klijenata ne sme da menja kontekst unutar žive transakcije, nego otvara novu transakciju po tenantu. Rebind u otvorenoj transakciji bi tiho radio nad pogrešnim tenantom.
Kako NG One postavlja obe granice
Ti troškovi su predvidivi i mereni. Trošak alternative, jednog curenja između dva klijenta, nije ni predvidiv ni popravljiv sledećim izdanjem. Zato obe granice u NG One-u stoje ispod svega ostalog — ispod svih devet poslovnih prostora i svih šezdesetak domena u njima. Izolacija nije modul koji se uključi nego osobina svake tabele i svakog granta u sistemu; kad se postavlja naknadno, postavlja se kao migracija nekoliko stotina tabela pod produkcijom.
- Deljena baza sa RLS: ENABLE i FORCE na svakoj tenant tabeli, politika u istoj migraciji kao tabela, ne u sledećoj.
- Kontekst se vezuje po zahtevu i postavlja na početku svake transakcije, pa umire sa njom; runtime rola je bez BYPASSRLS.
- Meta-test nad katalogom obara build kad tabela odstupi od šablona, uz eksplicitnu listu globalnih izuzetaka.
- Odvojena šema Control Plane-a, bez tenant RLS, sa sopstvenom DB rolom koja nema nijedan grant na poslovne tabele — i obrnuto.
- Platform Control Center je poseban portal sa posebnim auth klijentom i obaveznim MFA, a ne ekran u ERP-u i ne super tenant.
- Support access: obim, rok, odobrenje klijentovog administratora, dvostruki audit, automatski istek, izveštaj sesije.
- Maker-checker na suspenziji, gašenju tenanta, entitlement override-u i vanrednom pristupu.