Row Level Security: ko'p ijarachili tizimda izolyatsiyani baza darajasiga tushirish
Ko'p ijarachili (multi-tenant) tizim yozganingizda eng qo'rqinchli xato — bu qulash emas. Qulash ko'rinadi, log'da turadi, siz uni tuzatasiz. Haqiqiy qo'rquv boshqasi: bitta so'rovda WHERE organization_id = ? shartini yozishni unutasiz, hech narsa sinmaydi, tizim to'g'ri ishlayotgandek ko'rinadi — va bir mijoz ikkinchisining ma'lumotini ko'radi.
Maktab boshqaruv tizimini yozayotganda men shu xavf bilan uzoq yashadim. Har bir jadvalda tashkilot identifikatori bor, har bir so'rovda u qo'shilishi kerak. Bitta unutilgan joy yetadi. Ma'lumot esa jiddiy: o'quvchilar ro'yxati, baholar, davomat, ota-onalar aloqasi.
Bu maqola shu muammoni ilova qatlamidan baza qatlamiga tushirish haqida.
Muammo: intizom himoya emas
Odatiy yechim shunday ko'rinadi: repository qatlamida har bir metodga organizationId parametri kiritiladi va hamma joyda uni uzatish talab qilinadi.
Bu ishlaydi. To'g'rirog'i, hamma xatosiz yozganda ishlaydi.
Muammosi shundaki, bu intizomga tayanadi, mexanizmga emas. Yangi dasturchi jamoaga qo'shiladi, shoshilinch tuzatish kerak bo'ladi, kimdir tezkor SELECT yozadi — va himoya teshiladi. Bundan ham yomoni: teshik jimgina ochiladi. Hech qanday xato, hech qanday alert.
Qoida oddiy: agar xavfsizlik odamning har safar to'g'ri yozishiga bog'liq bo'lsa, u xavfsizlik emas, umid.
RLS nima qiladi
PostgreSQL'ning Row Level Security mexanizmi shartni so'rovdan olib, jadvalning o'ziga bog'laydi. Yoqilgandan keyin, kim qanday so'rov yozishidan qat'i nazar, baza faqat policy ruxsat bergan qatorlarni qaytaradi.
Eng oddiy shakli:
alter table students enable row level security;
create policy students_tenant_isolation on students
using (organization_id = current_setting('app.organization_id')::uuid);Endi select * from students yozsangiz ham faqat joriy tashkilot qatorlari keladi. WHERE shartini unutish endi ma'lumot sizishiga olib kelmaydi — u shunchaki bo'sh natija beradi.
Kontekst har ulanishda o'rnatiladi:
set local app.organization_id = '90e60e0c-4cb7-433f-b968-5f718ed3133a';set local — muhim tafsilot. U qiymatni faqat joriy tranzaksiya ichida ushlaydi. Oddiy set ishlatsangiz qiymat ulanishda qolib ketadi, ulanishlar esa pool orqali qayta ishlatiladi — va keyingi so'rov boshqa mijoz kontekstida noto'g'ri tashkilotni ko'radi. Bu RLS bilan bog'liq eng jimgina va eng xavfli xato.
Yozishni ham cheklash kerak
using sharti faqat o'qishni cheklaydi. Yozuvni ham yopish uchun with check kerak:
create policy students_insert on students
for insert
with check (organization_id = current_setting('app.organization_id')::uuid);Usiz foydalanuvchi boshqa tashkilot identifikatori bilan qator qo'sha oladi — o'zi keyin uni ko'rmaydi ham, lekin ma'lumot boshqa mijozning jadvalida paydo bo'ladi. Bu, ayniqsa, hisobotlarda uzoq vaqt sezilmay yuradigan turdagi xato.
Ikkita jiddiy tuzoq
Birinchisi — jadval egasi RLS'ga bo'ysunmaydi. Migratsiyalarni va ilova so'rovlarini bitta rol bilan bajarsangiz, RLS yoqilgan, policy yozilgan, lekin amalda hech narsa cheklanmaydi. Ilova alohida, egalik huquqiga ega bo'lmagan rol bilan ulanishi kerak.
Ikkinchisi — force row level security. Hujjatda u "egani ham majburlaydi" deb yozilgan va birinchi qarashda to'g'ri qadamdek tuyuladi. Lekin agar sizda security definer funksiyalar bo'lsa, ular funksiya egasi nomidan ishlaydi va force ularni ham bloklaydi. Men buni bloglagi izohlar tizimini yozayotganimda ko'rdim: hamma narsa lokalda ishlagan, force qo'shilgandan keyin barcha yozuv funksiyalari ruxsat xatosi bilan qulagan.
Qoida: force ni faqat security definer funksiyalaringiz yo'q bo'lsa yoqing.
RLS'siz variant: nol policy
Har doim ham policy yozish kerak emas. Blog uchun ko'rish soni va izohlarni saqlaydigan bazada men boshqacha yo'l tutdim: RLS yoqilgan, lekin birorta ham policy yozilmagan.
Natijasi qiziq — policy yo'q bo'lsa, RLS hech kimga hech narsa bermaydi. Ya'ni ommaviy kalit bilan bevosita bazaga kirgan odam nolinchi ruxsat oladi. Barcha so'rovlar esa server tomonidagi route handler orqali, xizmat roli bilan ketadi.
Bu ham to'liq haqiqiy izolyatsiya, faqat boshqa modelda: ma'lumotga kirish yagona darvozadan o'tadi, RLS esa yon eshiklarni yopadi.
Tanlov shunday: agar mijozlar bazaga bevosita ulansa — policy yozing. Agar hamma so'rov sizning serveringizdan o'tsa — RLS'ni "hamma narsa yopiq" holatida qoldirish soddaroq va ishonchliroq.
Policy'ni qanday testlash kerak
RLS'ni yozgandan keyin uni albatta sinash kerak, chunki xato jimgina ishlaydi. Men eng foydali deb topgan test shakli — ikkita tashkilot, bitta so'rov:
begin;
set local app.organization_id = 'aaaa...';
select count(*) from students; -- faqat A ning o'quvchilari
rollback;
begin;
set local app.organization_id = 'bbbb...';
select count(*) from students; -- faqat B ning o'quvchilari
rollback;Ikkala son ham to'g'ri bo'lsa va ularning yig'indisi umumiy sondan kelib chiqsa — izolyatsiya ishlayapti.
Ikkinchi muhim test — kontekstsiz so'rov. app.organization_id umuman o'rnatilmagan holatda so'rov yuboring. To'g'ri sozlangan tizimda u xato beradi yoki bo'sh natija qaytaradi. Agar to'liq jadvalni qaytarsa, demak policy amalda qo'llanmayapti — ehtimol siz jadval egasi rolidan foydalanyapsiz.
Narxi bormi
Bor, lekin ko'pchilik o'ylagandek emas. RLS sharti so'rovga qo'shiladi va rejalashtiruvchi uni oddiy WHERE kabi ko'radi. Ya'ni organization_id ustunida indeks bo'lsa — u ishlatiladi.
Amaliy tavsiya: ko'p ijarachili jadvallarda kompozit indeks tuzganda tashkilot ustunini birinchi qo'ying. (organization_id, created_at) deyarli har doim (created_at, organization_id) dan foydaliroq, chunki har bir so'rov baribir tashkilot bo'yicha filtrlanadi.
Xulosa
RLS sehr emas va u ilova qatlamidagi tekshiruvlarni bekor qilmaydi. U bitta aniq narsani beradi: tenant izolyatsiyasini unutib bo'lmaydigan qiladi.
Boshlash tartibi shunday. Avval ilovani egalik huquqi yo'q rol bilan ulang. Keyin bitta eng nozik jadvalda RLS yoqing va using + with check policy yozing. Kontekstni set local bilan, albatta tranzaksiya ichida uzating. Ikki tashkilot bilan test yozing va kontekstsiz holatni ham tekshiring. Ishonch hosil qilgach — qolgan jadvallarga tarqating.
Va force row level security ga tegishdan oldin security definer funksiyalaringiz bor-yo'qligini tekshiring. Bu bitta qator butun yozuv oqimini to'xtatib qo'yishi mumkin.