SaaS için PostgreSQL satır düzeyi güvenliği: işe yarayan politikalar
SaaS uygulamalarında PostgreSQL satır düzeyi güvenliği veritabanında kiracı izolasyonunu uygular. Ne zaman kullanılacağını, politikaların nasıl yazılacağını ve hangi hatalardan kaçınılacağını öğrenin.

RLS'in SaaS uygulamalarında çözmeye çalıştığı gerçek sorun
Bir SaaS uygulamasında en tehlikeli güvenlik hatası, ölçeklendikten sonra ortaya çıkan hatadır. Başlangıçta “kullanıcılar sadece kendi kiracılarına ait veriyi görebilir” gibi basit bir kuralla başlarsınız; sonra yeni bir uç nokta hızla yayınlanır, bir raporlama sorgusu eklenir veya kontrolü atlayan bir join ortaya çıkar.
Sadece uygulama tarafında yapılan yetkilendirme baskı altında başarısız olur çünkü kurallar dağınık hale gelir. Bir kontrolör tenant_id denetler, başka bir kontrolör üyeliği doğrular, bir arka plan işi unutur ve bir “admin dışa aktarma” yolu aylarca "geçici" kalır. Çok dikkatli ekipler bile bir noktayı gözden kaçırır.
PostgreSQL satır düzeyi güvenliği (RLS), veritabanının bir isteğe hangi satırların görünür olduğunu zorunlu kılmasını sağlayarak spesifik bir sorunu çözer. Zihinsel model basittir: her SELECT, UPDATE ve DELETE politikalarla otomatik olarak filtrelenir; tıpkı her isteğin kimlik doğrulama ara yazılımı ile filtrelenmesi gibi.
“Satırlar” kısmı önemlidir. RLS her şeyi korumaz:
- Kendi başına belirli sütunları gizlemez (görünümler veya sütun ayrıcalıkları kullanın).
- Güvensiz fonksiyonları güvenli hale getirmez (bir fonksiyon yükseltilmiş haklarla çalışırsa yine veri sızdırabilir).
- İş kurallarını doğrulamaz (örneğin, “sadece sahipler fatura ayarlarını değiştirebilir”).
Somut bir örnek: Dashboard için projeleri listelerken invoices ile join yapan bir uç nokta eklersiniz. Sadece uygulama yetkilendirmesiyle projects için tenant filtresi uygulayıp invoices için filtrelemeyi unutmak veya tenantları çaprazlayan bir anahtarda join yapmak kolaydır. RLS ile her iki tablo da kiracı izolasyonunu zorunlu kılabilir, böylece sorgu veri sızdırmak yerine güvenli bir şekilde başarısız olur.
Takas gerçek. Tekrarlanan yetkilendirme kodunu daha az yazarsınız ve sızıntı yaratabilecek yerlerin sayısını azaltırsınız. Ancak yeni işler de yüklenir: politikaları dikkatle tasarlamalı, erken test etmeli ve bir politikanın beklediğiniz bir sorguyu engelleyebileceğini kabul etmelisiniz.
RLS yetkilendirmeyi ne zaman basitleştirir (ve ne zaman sorun ekler)
Uygulamanız bir avuç uç noktanın ötesine geçene kadar RLS ekstra iş gibi gelebilir. Katı kiracı sınırlarınız ve çok sayıda sorgu yolu (liste ekranları, arama, dışa aktarımlar, yönetici araçları) varsa kurali veritabanına koymak, aynı filtreyi her yere eklemeyi hatırlama zorunluluğunu ortadan kaldırır.
RLS, kural sıkıcı ve evrensel olduğunda güçlü bir uyum sağlar: “bir kullanıcı yalnızca kendi kiracısına ait satırları görebilir” veya “bir kullanıcı yalnızca üyesi olduğu projeleri görebilir” gibi. Bu kurulumlarda politikalar hataları azaltır çünkü her SELECT, UPDATE ve DELETE aynı kapıdan geçer, hatta sorgu sonradan eklendiğinde bile.
Ayrıca filtreleme mantığının tutarlı kaldığı okuma-ağırlıklı uygulamalarda yardımcı olur. API'nizde faturaları yüklemenin 15 farklı yolu varsa (duruma, tarihe, müşteriye, aramaya göre), RLS her sorguda kiracı filtresini yeniden uygulamayı bırakmanızı sağlar ve özelliğe odaklanmanıza izin verir.
Kurallar satır bazlı olmadığında RLS acı verir. “Maaşı görebilirsin ama primi göremezsin” veya “bu sütunu İK değilse maskele” gibi alan bazlı kurallar genellikle garip SQL'lere ve zor bakılan istisnalara dönüşür.
Ayrıca gerçekten geniş erişim gerektiren ağır raporlama için uygun bir uyum değildir. Ekipler genellikle “sadece bu iş için” atlatma rolleri oluşturur ve hata birikimi burada başlar.
Karar vermeden önce veritabanının son gardiyan olup olmayacağına karar verin. Eğer evet ise disiplin planlayın: veritabanı davranışını test edin (sadece API yanıtlarını değil), göçleri güvenlik değişikliği olarak değerlendirin, hızlı atlatmalardan kaçının, arka plan işlerinin nasıl kimlik doğrulayacağını belirleyin ve politikaları küçük ve tekrarlanabilir tutun.
Eğer backend üreten araçlar kullanıyorsanız, teslimatı hızlandırabilirler ama açık roller, testler ve basit bir kiracı modeline olan ihtiyacı ortadan kaldırmazlar. (Örneğin, Koder.ai Go ve PostgreSQL ile üretilmiş backendler kullanır ve yine de RLS'i sonradan “serpiştirmek” yerine kasıtlı tasarlamak istersiniz.)
RLS politikalarını yönetilebilir kılan veri modeli temelleri
Şema zaten kimin neye sahip olduğunu açıkça söylüyorsa RLS en kolay haldedir. Bulanık bir modelle başlayıp “politikalarda düzeltmeye” çalışırsanız genellikle yavaş sorgular ve kafa karıştıran hatalar elde edersiniz.
İlgili her yere bir tenant anahtarı koyun
Bir tenant anahtarını (ör. org_id) seçin ve tutarlı kullanın. Çoğu tenant-sahibi tablo bunu içermelidir; hatta başka bir tabloya referans verse bile. Bu, politikalar içinde join'leri önler ve USING kontrollerini basit tutar.
Pratik kural: bir satır müşteri iptal ettiğinde kaybolması gerekiyorsa muhtemelen org_id'ye ihtiyaç duyar.
Üyelikleri açıkça modelleyin
RLS politikaları genellikle bir soruya cevap verir: “Bu kullanıcı bu org'un üyesi mi ve ne yapabilir?” Bunu rastgele sütunlardan çıkarmak zordur.
Çekirdek tabloları küçük ve sıkıcı tutun:
users(kişiye bir satır)orgs(her tenant için bir satır)org_memberships(user_id,org_id,role,status)- isteğe bağlı: proje bazlı erişim için
project_memberships
Bunlarla politikalarınız üyeliği tek bir indeksli sorguyla kontrol edebilir.
Paylaşılan referans verisini tenant-sahibi veriden ayırın
Her şey org_id gerektirmez. Ülkeler, ürün kategorileri veya plan tipleri gibi referans tablolar genelde tüm kiracılar arasında paylaşılan verilerdir. Bunları çoğu rol için salt okunur yapın ve tek bir org'a bağlamayın.
Tenant-sahibi veriler (projeler, faturalar, ticket'lar) paylaşılan tablolardan kiraca özel detayları çekmemelidir. Paylaşılan tabloları minimal ve stabil tutun.
FK'lar, cascade'ler ve indexleme
Foreign key'ler RLS ile hala çalışır, ancak silmeler sizi şaşırtabilir çünkü silme yapan rol bağlı satırları “göremeyebilir”. Cascade'leri dikkatle planlayın ve gerçek silme akışlarını test edin.
Policy'ların filtrelediği sütunları, özellikle org_id ve üyelik anahtarlarını indeksleyin. WHERE org_id = ... gibi görünen bir politika tablo milyonlarca satıra ulaşınca tam tablo taraması olmamalıdır.
RLS politikaları korkutucu tarafları olmadan nasıl çalışır
RLS tablo başına açılan bir anahtardır. Bir kez etkinleştirildiğinde PostgreSQL uygulama kodunun kiracı filtresini hatırlamasını güvenilmez sayar. Her SELECT, UPDATE ve DELETE politikalar tarafından filtrelenir, her INSERT ve UPDATE ise politikalar tarafından doğrulanır.
En büyük zihinsel değişim: RLS açıkken eskiden veri döndüren sorgular artık hata vermeden sıfır satır döndürebilir. Bu PostgreSQL'in erişim kontrolü uygulaması.
Politikalar gerçekte ne yapar
Politikalar tabloya bağlı küçük kurallardır. İki kontrol kullanırlar:
USINGokuma filtresidir. Bir satırUSINGile eşleşmiyorsaSELECTiçin görünmez veUPDATEveyaDELETEtarafından hedeflenemez.WITH CHECKyazma kapısıdır.INSERTveyaUPDATEsırasında yeni veya değişen satırların hangi şartları sağlaması gerektiğini belirler.
Yaygın bir SaaS deseni: USING sadece kendi kiracınızın satırlarını görmenizi sağlar, WITH CHECK ise tahminle gönderilen bir tenant ID ile başkasına satır eklenmesini engeller.
PERMISSIVE vs RESTRICTIVE, tek cümlede
Daha sonra daha fazla politika eklendiğinde bu önem kazanır:
PERMISSIVE(varsayılan): herhangi bir politika satırı izin veriyorsa satır kabul edilir.RESTRICTIVE: yalnızca tüm kısıtlayıcı politikalar izin veriyorsa satır kabul edilir (permissive davranışın üzerine ek olarak).
Tenant eşleşmesi + rol kontrolleri + proje üyeliği gibi katmanlı kurallar koymayı planlıyorsanız, restrictive politikalar niyeti daha net yapabilir; ancak bir koşulu unutursanız kendinizi kilitlemek de kolaylaşır.
Postgres çağıranın kim olduğunu nasıl bilir
RLS güvenilir bir “kim çağırıyor” değeri gerektirir. Yaygın seçenekler:
- Her istek için ayarlanan bir oturum değişkeni (örneğin
app.user_idveapp.tenant_id). - API katmanı tarafından oturum ayarlarına eşlenen JWT claim'leri.
- Rol değiştirme (
SET ROLE ...her istek için), çalışabilir ama işletme bakım yükü ekler.
Bir yaklaşım seçin ve her yerde uygulayın. Hizmetler arasında kimlik kaynaklarını karıştırmak hızlıca kafa karıştıran hatalara yol açar.
Politikaları adlandırma ki sonradan debug kolay olsun
Şema dökümleri ve loglar okunaklı kalsın diye öngörülebilir bir konvansiyon kullanın. Örneğin: {table}__{action}__{rule}, mesela projects__select__tenant_match.
Adım adım: bir tabloya RLS ekleyin ve çalıştığını kanıtlayın
RLS'e yeniyseniz bir tabloyla başlayın ve küçük bir kanıt yapın. Amaç mükemmel kapsam değil. Amaç, uygulama hatası olsa bile veritabanının kiracılar arası erişimi reddetmesidir.
Pratik yapmak için küçük bir tablo
Basit bir projects tablosu varsayın. İlk olarak, yazmaları bozmadan tenant_id ekleyin.
ALTER TABLE projects ADD COLUMN tenant_id uuid;
-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;
ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;
Sonra sahipliği erişimden ayırın. Yaygın bir desen: tablolar bir rol tarafından sahiplenilir (app_owner), API ise başka bir rol ile erişir (app_user). API rolü tablo sahibi olmamalıdır, aksi halde politikaları atlayabilir.
ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;
Şimdi isteğin Postgres'e hangi kiracıyı hizmet ettiğini söyleyeceğine karar verin. Basit bir yaklaşım istek kapsamlı bir ayar kullanmaktır. Uygulamanız bunu bir işlem açtıktan hemen sonra ayarlar.
-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);
RLS'i etkinleştirin ve önce okuma erişimi ile başlayın.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);
Farklı iki kiracıyla deneyip satır sayısının değiştiğini kontrol ederek çalıştığını kanıtlayın.
Yazma kuralları ekleyin (WITH CHECK)
Okuma politikaları yazmaları korumaz. Ekleyin ki insert ve update işlemleri yanlış kiracıya satır sokamasın.
CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Davranışı (hatalar dahil) doğrulamanın hızlı bir yolu, her göçten sonra yeniden çalıştırabileceğiniz küçük bir SQL betiği tutmaktır:
BEGIN; SET LOCAL ROLE app_user;SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad');(başarısız olmalı)UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...;(başarısız olmalı)ROLLBACK;
Bu betiği her çalıştırdığınızda aynı sonuçları alabiliyorsanız, RLS'i diğer tablolara genişletmeden önce güvenilir bir temeliniz vardır.
Çoğu SaaS uygulamasında yeniden kullanacağınız politika desenleri
Çoğu ekip aynı yetkilendirme kontrollerini her sorguda tekrar etmekten bıkınca RLS kullanmaya başlar. İyi haber, ihtiyaç duyacağınız politika şekilleri genelde tutarlıdır.
Sahiplik satırları vs üyelik satırları
Bazı tablolar doğal olarak bir kullanıcıya aittir (notlar, API token'ları). Diğerleri bir kiracıya aittir ve erişim üyeliğe bağlıdır. Bunları farklı desenler olarak ele alın.
Sahip-e yönelik veriler için politikalar genellikle created_by = app_user_id() gibi kontroller yapar. Kiracı verileri için politikalar genellikle kullanıcının org için bir üyelik satırı olup olmadığını kontrol eder.
Politikaları okunabilir tutmanın pratik bir yolu kimliği küçük SQL yardımcılarında merkezileştirmek ve yeniden kullanmaktır:
-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
select current_setting('app.user_id', true)::uuid
$$;
create function app_is_admin() returns boolean
language sql stable as $$
select current_setting('app.is_admin', true) = 'true'
$$;
Okuma kurallarını yazma kurallarından ayırın
Okumalar genelde yazmalardan daha geniştir. Örneğin, herhangi bir org üyesi projeleri SELECT edebilir, ama sadece editörler UPDATE yapabilir ve sadece sahipler DELETE yapabilir.
Açık tutun: bir SELECT politikası (üyelik), bir INSERT/UPDATE politikası WITH CHECK ile (rol) ve bir DELETE politikası (genelde update'ten daha sıkı) şeklinde ayırın.
Admin override — RLS'i kapatmadan
“Adminler için RLS'i kapat” yerine, app_is_admin() gibi politikalar içinde bir kaçış yolu ekleyin; böylece paylaşılan bir servis rolüne kazara tam erişim vermemiş olursunuz.
Soft delete ve durum bayrakları
deleted_at veya status kullanıyorsanız bunu SELECT politikasına dahil edin (deleted_at is null). Aksi halde uygulamanın final kabul ettiği bayrakları biri değiştirerek satırları "diriltebilir".
UPSERT: WITH CHECK ile uyumlu tutun
INSERT ... ON CONFLICT DO UPDATE yazma sonrası satır için WITH CHECK'i sağlamalıdır. Politikanız created_by = app_user_id() gerektiriyorsa, upsert insert sırasında created_by'ı ayarlamalı ve update sırasında onu üzerine yazmamalıdır.
Eğer backend kodu üretiyorsanız, bu desenler yeni tabloların güvenli varsayılanlarla başlamasını sağlamak için dahili şablonlara dönüştürmeye değer.
RLS’in yaygın tehlikeli tuzakları ve hata ayıklamayı zorlaştıranlar
RLS harika ama küçük bir detay PostgreSQL’in veriyi “rastgele” saklıyormuş gibi görünmesine yol açabilir. Aşağıdaki hatalar en çok zaman kaybettirir.
Sessiz kiracı sızıntılarına yol açan tuzaklar
İlk tuzak, INSERT ve UPDATE için WITH CHECK'i unutmaktır. USING sadece neyi görebileceğinizi kontrol eder, ne oluşturabileceğinizi değil. WITH CHECK olmadan bir uygulama hatası yanlış kiracıya satır yazabilir ve aynı kullanıcı bunu geri okuyamadığı için fark etmeyebilirsiniz.
Bir diğer yaygın sızıntı “sızdıran join”dir. projects'u doğru filtrelersiniz, sonra invoices, notes veya files ile join yaparsınız ancak onlar aynı şekilde korunmamıştır. Çözüm katıdır ama basittir: kiracı verisini açığa çıkarabilecek her tablo kendi politikasına ihtiyaç duyar; view'lar yalnızca bir tablonun güvenli olmasına dayanarak yazılmamalıdır.
Yaygın hata örüntüleri erken çıkar:
- Bir okuma politikası var ama yazma politikası
WITH CHECKeksik. - Bir politika korunmayan başka bir tabloya join yapan bir koşul kullanır.
- Erişim bir view içinde sağlanıyor ama altta yatan tablo hâlâ açık.
- "Uygulama her zaman tenant_id koyar" varsayımına güvenilir ve bir arka plan işi unutur.
- Testlerinizi superuser rolüyle yapıyorsunuz, bu yüzden gerçek davranışı görmüyorsunuz.
Davranışı kafa karıştıran tuzaklar
Aynı tabloya referans veren politikalar (doğrudan veya view aracılığıyla) döngüsel sürprizler yaratabilir. Bir politika üyeliği başka bir view üzerinden kontrol ediyorsa ve o view korunan tabloyu tekrar okursa hatalar, yavaş sorgular veya hiçbir zaman eşleşmeyen bir politika oluşabilir.
Rol ayarları başka bir kafa karışıklığı kaynağıdır. Tablo sahipleri ve yükseltilmiş roller RLS'i atlayabilir, bu yüzden testleriniz geçerken gerçek kullanıcılar başarısız olabilir (veya tam tersi). Her zaman uygulamanın kullandığı düşük ayrıcalıklı rol ile test edin.
SECURITY DEFINER fonksiyonlarına dikkat edin. Bunlar fonksiyon sahibinin ayrıcalıkları ile çalışır, bu yüzden current_tenant_id() gibi bir yardımcı genelde güvenli olabilir, ama veriyi okuyan “kolaylık” fonksiyonları tenantlar arası okumaya izin verebilir; tasarımını RLS'e saygılı yapın.
Ayrıca security definer fonksiyonlar içinde güvenli bir search_path ayarlayın. Ayarlamazsanız fonksiyon aynı ada sahip farklı bir nesneyi yakalayabilir ve politika mantığınız oturum durumuna bağlı olarak yanlış şeye işaret edebilir.
RLS hata ayıklama: ne olduğunu görmenin pratik yolları
RLS hataları genelde bağlam eksikliğinden kaynaklanır, kötü SQL'den değil. Bir politika kağıt üzerinde doğru olsa bile oturum rolü sandığınızdan farklı olabilir veya istek politikaların beklediği tenant ve kullanıcı değerlerini hiç ayarlamıyor olabilir.
Üretim bir raporu yeniden üretmenin güvenilir yolu, aynı oturum ayarlarını yerelde taklit etmek ve tam sorguyu çalıştırmaktır. Bu genelde şunları içerir:
SET ROLE app_user;(veya gerçek API rolü)SELECT set_config('app.tenant_id', 't_123', true);veSELECT set_config('app.user_id', 'u_456', true);- Uygulamanın çalıştırdığı aynı SQL'i çalıştırın (parametreler dahil)
- Postgres'in gördüğünü doğrulayın:
SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);
Hangi politikanın uygulandığından emin değilseniz tahmin etmek yerine katalogu kontrol edin. pg_policies her politikayı, komutu ve USING ile WITH CHECK ifadelerini gösterir. Bunu pg_class ile eşleştirip tabloda RLS'in etkin olup olmadığını ve atlanıp atlanmadığını doğrulayın.
Performans sorunları yetki sorunları gibi görünebilir. Bir politika üyelik tablosuna join yapıyor veya bir fonksiyon çağırıyorsa doğru olabilir ama tablo büyüdüğünde yavaşlayabilir. Yeniden üretilen sorguda EXPLAIN (ANALYZE, BUFFERS) kullanın ve tam tablo taramaları, beklenmeyen nested loop'lar veya filtrelerin geç uygulanması gibi şeylere bakın. (tenant_id, user_id) ve üyelik tablolarındaki eksik indeksler yaygın sebeplerdir.
Ayrıca app katmanında her istekte üç değeri loglamak yardımcı olur: tenant ID, user ID ve istek için kullanılan veritabanı rolü. Bunlar düşündüğünüz gibi ayarlı değilse, RLS “yanlış” davranır çünkü girdiler yanlıştır.
Testler için birkaç seed tenant tutun ve başarısızlıkları açık hale getirin. Küçük bir test süiti genellikle şunları içerir: “Tenant A, Tenant B'yi okuyamaz”, “üyeliği olmayan kullanıcı projeyi göremez”, “sahip güncelleyebilir, viewer güncelleyemez”, “insert bağlam eşleşmiyorsa engellenir” ve “admin override sadece hedeflendiği yerde uygulanır”.
Üretime alma öncesi hızlı kontrol listesi
RLS'i bir emniyet kemeri gibi değerlendirin, bir özellik anahtarı gibi değil. Küçük eksiklikler “herkes herkesin verisini görebilir” veya “her şey sıfır satır döner” sonuçlarına yol açabilir.
Veri modeli ve politika şekli
Tablo tasarımınız ve politika kurallarınız kiracı modelinizle eşleşsin.
- Her tenant-sahibi tablo açık bir tenant anahtarına (genelde
tenant_id) sahip olmalı. Eğer yoksa nedenini yazın (ör. global referans tablolar). - RLS'i sadece “ana” tablolarda değil her tenant-sahibi tabloda etkinleştirin. Bazı yolların kesinlikle atlanmaması gerekiyorsa o tablolar için
FORCE ROW LEVEL SECURITYdüşünün. - Okuma ve yazma kurallarını ayırın. Okumalar
USINGile, yazmalarWITH CHECKile olmalı ki insert ve update başka bir kiracıya satır taşıyamasın. - Politika öbeklerini indeks dostu tutun. Politikalarda
tenant_idile filtreleme veya üyelik tabloları üzerinden join varsa uygun indeksleri ekleyin.
Basit bir sağlık senaryosu: tenant A kullanıcısı kendi faturalarını okuyabilir, sadece tenant A için fatura ekleyebilir ve faturayı tenant_id'yi değiştirerek güncelleyemez.
Roller, performans ve testler
RLS, uygulamanızın kullandığı roller kadar güçlüdür.
- Uygulamanın asla superuser, tablo sahibi veya
bypassrlshakkına sahip bir rol ile bağlanmadığından emin olun. - Üretime benzer veri hacmiyle birkaç gerçek sorgu çalıştırın ve sorgu planlarını kontrol edin.
- Çapraz-kiracı erişiminin başarısız olduğunu kanıtlayan otomatik negatif testler ekleyin.
Örnek: üyeliğe dayalı erişimi olan çok kiracılı projeler uygulaması
Şirketlerin (org) projeleri olduğu ve projelerin görevleri olduğu bir B2B uygulamasını düşünün. Kullanıcılar birden fazla org'a üye olabilir ve bir kullanıcı bazı projelerin üyesi olmayabilir. Bu, RLS için iyi bir uyumdur çünkü veritabanı API uç noktası bir filtreyi unuttuğunda bile kiracı izolasyonunu zorunlu kılabilir.
Basit model: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). tasks üzerindeki org_id kasıtlıdır. Politikaları basit tutar ve join'lerde sürprizleri azaltır.
Klasik bir sızıntı, görevlerin sadece project_id'ye sahip olmasıdır ve erişimi projects üzerinden join ile kontrol edersiniz. Bir hata (projects üzerinde izin veren bir politika, join sırasında bir koşulun düşmesi veya bir view'ın bağlamı değiştirmesi) diğer org'ların görevlerini açığa çıkarabilir.
Daha güvenli bir geçiş yolu üretim trafiğini bozmadan şöyle olabilir:
- Önce şema değişikliklerini gönderin (
tasks'aorg_idekleyin, üyelik tablolarını ekleyin). tasks.org_id'yiprojects.org_id'den backfill yapın, sonraNOT NULLekleyin.- Politikaları ekleyin ama staging'de test ederken RLS'i devre dışı tutun.
- RLS'i etkinleştirin, sonra zorlayın ve ancak ondan sonra eski uygulama tarafı filtrelerini kaldırın.
Destek erişimi genellikle RLS'i devre dışı bırakmak yerine dar bir break-glass rolü ile yönetilir. Bunu normal destek hesaplarından ayırın ve ne zaman kullanıldığını açıkça kaydedin.
Kuralları belgeleyin: hangi oturum değişkenlerinin ayarlanması gerektiği (user_id, org_id), hangi tabloların org_id taşıması gerektiği, “üye” tanımının ne olduğu ve yanlış org ile çalıştırıldığında 0 satır döndürmesi gereken birkaç SQL örneği.
Sonraki adımlar: RLS'i güvenle devreye alın ve sürdürülebilir tutun
RLS, bir ürün değişikliği gibi ele alındığında daha kolay yaşanır. Küçük parçalar halinde dağıtın, testlerle davranışı kanıtlayın ve her politikanın neden var olduğuna dair net bir kayıt tutun.
İşleyen bir rollout planı genelde şöyle olur:
- Net kiracı sahipliğine sahip bir tabloyla başlayın (örneğin
projects) ve onu kilitleyin. - Birkaç rol (sahip, üye, dışarıdan) için izin verilen ve engellenen okuma/yazma durumlarını kapsayan testler ekleyin.
- Bölümler halinde genişletin (her seferinde tek bir özelliği) ki tek bir oturumda hataları ayıklayabilesiniz.
- Rollout sırasında izin hatalarını izleyin ve düşük riskli bir pencerede dağıtın.
İlk tablo stabil olduktan sonra politika değişikliklerini kasıtlı yapın. Göçlere bir politika inceleme adımı ekleyin ve niyeti (kimin, neye ve neden erişmesi gerektiği) kısa bir notla beraber bir eşleyen test değişikliğini ekleyin. Bu, “bir OR daha ekle” şeklinde politikaların yavaşça delik haline gelmesini önler.
Hızlı hareket ediyorsanız, Koder.ai (koder.ai) gibi araçlar Go + PostgreSQL başlangıç noktası üretmenizde yardımcı olabilir; ardından aynı disiplinle RLS politikaları ve testleri ekleyin.
Son olarak, rollout sırasında emniyet rayları tutun. Politika göçlerinden önce snapshot alın, rollback alıştırmasını sık yapın ve tüm sistemi kapatmadan kullanılacak küçük bir break-glass yolu tutun.
SSS
What security problem does RLS actually solve in a SaaS app?
RLS, PostgreSQL'in bir isteğe hangi satırların görülebileceğini veya yazılabileceğini zorunlu kılmasını sağlar; böylece kiracı izolasyonu her uç noktanın doğru WHERE tenant_id = ... filtresini hatırlamasına bağlı olmaz. Ana kazanım, uygulamanız büyüdükçe ve sorgular çoğaldıkça “bir eksik kontrol” hatalarını azaltmaktır.
When is RLS worth the extra complexity?
Erişim kuralları tutarlı ve satır tabanlıysa (ör. kiracı izolasyonu veya üyeliğe dayalı erişim) ve çok sayıda sorgu yolu (arama, dışa aktarma, admin ekranları, arka plan işleri) varsa genellikle buna değer. Çoğu kural alan alanına özel, çok istisnaya dayalı veya geniş çaplı raporlama gerektiren durumlarda genelde değmez.
What does RLS NOT protect me from?
RLS'i satır görünürlüğü ve temel yazma kısıtlamaları için kullanın; diğer ihtiyaçlar için ek araçlar gerekir. Sütun gizliliği genellikle view'lar ve sütun ayrıcalıkları ile ele alınır; karmaşık iş kuralları (fatura sahipliği veya onay akışları gibi) hâlâ uygulama mantığında veya özenle tasarlanmış veritabanı kısıtlarında olmalıdır.
What’s the safest way to start using RLS if I’m new to it?
API için düşük ayrıcalıklı bir rol oluşturun (tablo sahibi olmayan), RLS'i etkinleştirin, sonra bir SELECT politikası ve WITH CHECK içeren bir INSERT/UPDATE politikası ekleyin. İstek kapsamlı bir oturum değeri (ör. app.current_tenant) ayarlayın ve bunu değiştirmenin hangi satırları görebildiğinizi ve yazabildiğinizi nasıl etkilediğini doğrulayın.
How should my app tell Postgres which tenant and user is making the request?
Yaygın bir varsayılan, her istek için bir oturum değişkenidir; örneğin işleme başladığınızda app.tenant_id ve app.user_id gibi değerler ayarlanır. Anahtar nokta tutarlılıktır: tüm kod yolları (web istekleri, işler, betikler) politikaların beklediği aynı değerleri ayarlamalıdır, aksi halde kafa karıştırıcı “sıfır satır” davranışıyla karşılaşırsınız.
What’s the difference between USING and WITH CHECK in an RLS policy?
USING mevcut satırların hangi koşullarda görüneceğini ve SELECT, UPDATE, DELETE tarafından hedef alınabileceğini kontrol eder. WITH CHECK ise INSERT ve UPDATE sırasında yeni veya değişen satırların hangi şartları sağlaması gerektiğini belirler; böylece hatalı bir tenant_id ile başka bir kiracıya yazmayı engeller.
Why do people keep saying “don’t forget WITH CHECK”?
USING eklediğinizde ama WITH CHECK yokken, hatalı bir uç nokta yine de başka bir kiracıya veri yazabilir ve bunu fark etmeyebilirsiniz çünkü aynı kullanıcı o satırı geri okumaya yetkili olmayabilir. Yazma kurallarını okumaya karşılık gelen WITH CHECK ile eşleştirmeden sakın.
How should I structure my schema so RLS policies stay simple and fast?
Politikaları karmaşıklaştırmadan okunabilir tutmanın bir yolu, kiracı anahtarını (org_id gibi) doğrudan kiracıya ait tablolarda bulundurmaktır; bu sayede politikalar tek bir dizinli okuma ile üyeliği kontrol edebilir. Ayrıca org_memberships ve gerekirse project_memberships gibi açık üyelik tabloları ekleyin.
How do I debug “RLS is hiding my data” without guessing?
Önce aynı oturum bağlamını taklit ederek üretim raporunu yerelde yeniden oluşturun: aynı rolü ayarlayın, aynı oturum değişkenlerini set_config ile verin ve uygulamanın çalıştırdığı tam SQL'i yürütün. Sonra pg_policies'i kontrol ederek hangi USING ve WITH CHECK ifadelerinin uygulandığını görün; çoğu RLS hatası kimlik bağlamının eksik olmasından kaynaklanır, kötü SQL’den değil.
If I generate my backend (for example with Koder.ai), do I still need to design RLS carefully?
Evet — ama üretilen kodu bir başlangıç noktası olarak görün, güvenlik sistemi olarak değil. Koder.ai ile bir Go + PostgreSQL backend üretseniz bile, kendi kiracı modelinizi tanımlamanız, oturum kimliğini tutarlı ayarlamanız ve yeni tabloların doğru korumalarla gelmesini sağlamak için politikalar ve testler eklemeniz gerekir.