Multi-Tenancy, Kütüphaneler ve Mimari Kararlar
İzin sisteminize multi-tenant izolasyonu ekleyin, CASL'ı bir kütüphane alternatifi olarak değerlendirin ve doğru yetkilendirme mimarisini seçmek için karar çerçevelerini kullanın.
Abstract
Post 101 bir izin sistemi için yedi hedef belirledi ve dağınık kontrol anti-pattern'ini ortaya koydu. Post 102 yetkilendirmeyi bir service layer içinde merkezileştirdi. Post 103 type-safe RBAC ekledi. Post 104 rol-izin matrisini ABAC policy engine ile değiştirdi. Post 105 ABAC'ı çevre koşulları, alan düzeyinde okuma/yazma izinleri ve veritabanı sorgu filtreleme ile genişletti.
Üç üretim sorunu kaldı. Birincisi, sistemin tenant sınırı yok -- Organizasyon A'daki bir kullanıcı, doğru sorgu ile Organizasyon B'nin kaynaklarına erişebilir. İkincisi, özel ABAC motoru çalışıyor ama şu soru ortaya çıkıyor: ekip özel yetkilendirme kodunu mu sürdürmeli yoksa CASL gibi bir kütüphaneye mi geçmeli? Üçüncüsü, RBAC, özel ABAC, kütüphane tabanlı ABAC ve harici policy engine'ler arasında seçim yapmak için kapsamlı bir karar çerçevesi yok.
Bu kapanış yazısı üç boşluğu kapatıyor: birinci sınıf bir izin kavramı olarak multi-tenancy, dürüst sürtünme analizi ile çalışan bir CASL geçişi ve serinin tüm yaklaşımları kapsayan kesin karşılaştırması.
Multi-Tenancy Modelleri
Tenant Kavramı
SaaS'ta bir tenant, kullanıcıları ve kaynaklarını gruplayan bir organizasyon, çalışma alanı veya hesaptır. Slack çalışma alanları, GitHub organizasyonları ve Notion çalışma alanları birer tenant'tır. Tenant sınırı, en dıştaki izin sınırıdır -- rolleri, sahipliği veya alan erişimini kontrol etmeden önce, sistem kullanıcının kaynağın sahibi olan tenant'a ait olduğunu doğrulamalıdır.
Domain Modelini Genişletme
Serinin domain modeli bir tenant boyutu kazanıyor:
Her kaynak artık bir tenantId taşıyor. Her kullanıcı tam olarak bir tenant'a ait (basitlik için -- çoklu tenant üyeliği mümkün ama bu kapsamın dışında karmaşıklık ekliyor).
Üç İzolasyon Stratejisi
Satır Düzeyi İzolasyon (paylaşılan şema): Tüm tenant'lar aynı tabloları paylaşır. Her tabloda bir tenant_id sütunu bulunur. En basit altyapı ve en ucuz seçenek, ancak bir WHERE tenant_id = ? koşulunu unutmak çapraz tenant veri sızıntısına neden olur. PostgreSQL Row-Level Security (RLS), veritabanı düzeyinde bir güvenlik ağı olarak bunu zorunlu kılabilir.
Şema Düzeyi İzolasyon: Her tenant aynı veritabanı içinde ayrı bir şema alır. Daha güçlü izolasyon -- eksik WHERE koşulu veri sızıntısı yerine hata üretir. Migration'lar N şema üzerinde çalıştırılmalıdır. Onlarca ila yüzlerce tenant için uygundur.
Veritabanı Düzeyi İzolasyon: Her tenant özel bir veritabanı örneği alır. Maksimum izolasyon ve en güçlü uyumluluk duruşu. En yüksek maliyet ve operasyonel karmaşıklık.
Bu seri satır düzeyi izolasyon modelini kullanıyor çünkü en yaygın başlangıç noktası ve izin perspektifinden en zorlu olanı. Şema ve veritabanı izolasyonu tenant sınırlarını altyapı düzeyinde çözer. Satır düzeyi izolasyon, bu sınırları uygulamanın zorunlu kılmasını gerektirir.
Tenant-Aware İzin Katmanı
Dağınık Tenant Kontrolü Anti-Pattern'i
Tenant-aware izinler olmadan, her service metodu tenant izolasyonunu manuel olarak kontrol eder:
Bu, Post 101'deki dağınık kontrol pattern'inin tenant düzeyinde yeniden ortaya çıkmasıdır. Bir geliştirici tek bir endpoint'te tenant kontrolünü unutursa, çapraz tenant veri sızıntısı oluşturur -- diğer müşterilerin verilerini ifşa ettiği için en tehlikeli yetkilendirme hatası sınıfıdır.
Tenant İzolasyonunu Global ABAC Koşulu Olarak Tanımlama
Doğru yaklaşım: tenant izolasyonu, her izin kontrolünde otomatik olarak çalışan yerleşik bir koşul haline gelir:
global() koşulu herhangi bir role-specific koşuldan önce çalışır. Her izin kontrolünde örtük bir WHERE koşulu gibi davranır. Bir geliştirici yeni bir rol veya yeni bir kaynak türü oluştursa bile, tenant izolasyonu otomatik olarak zorunlu kılınır.
can() Fonksiyonunu Güncelleme
can() fonksiyonu önce global koşulları değerlendirir:
Veritabanı Sorgu Filtrelemesini Güncelleme
Post 105'teki toWhereClause() fonksiyonu tenant filtreleme içermelidir:
Tenant filtresi her zaman mevcuttur. buildRoleFilter() {} döndürse bile (admin için ek filtre yok), sorgu yine de WHERE tenantId = ? içerir.
Çapraz Tenant Erişimi: İstisna
Bazı senaryolar çapraz tenant erişimi gerektirir:
- Platform yöneticileri (süper adminler) tüm tenant'ları yöneten
- Paylaşılan kaynaklar (şablonlar, herkese açık içerik) herhangi bir tenant'ın dışında var olan
- Destek araçları müşteri hizmetlerinin tenant verilerini görüntülemesi için
Warning: Çapraz tenant istisnaları açık ve denetlenebilir olmalıdır. Platform yöneticisi rolü, tenant düzeyindeki yöneticiden ayrı olmalı ve Post 105'teki çevre koşulları ile zorunlu kılınan ek kimlik doğrulama gereksinimleri (MFA, IP kısıtlamaları) içermelidir.
Neden Bir İzin Kütüphanesi Kullanmalı?
Build vs. Kütüphane Kararı
Post 101-105, RBAC, ABAC, alan düzeyinde izinler, DB sorgu filtreleme, çevre kuralları ve şimdi multi-tenancy'yi kapsayan özel bir izin sistemi oluşturdu. Bu yaklaşık 300-500 satır çekirdek izin mantığıdır. Bu kodu sürdürmek ne noktada bir kütüphane benimsemekten daha pahalı hale gelir?
Özel Uygulama Güçlü Yönleri
- Sıfır bağımlılık: Kritik güvenlik yolunda üçüncü taraf kodu yok
- API yüzeyinde tam kontrol:
can()imzası tam olarak gerektiği gibi gelişir - Mükemmel TypeScript entegrasyonu: Generic constraint'ler, builder pattern'ler ve spesifik domain için tasarlanmış type inference
- Serileştirme yükü yok: Düz fonksiyonlar, class instance'ları yok, RSC uyumlu
- Ekip anlayışı: Her koşul ekibin yazdığı bir fonksiyon -- kara kutu yok
- Öngörülebilir davranış: Hata ayıklama standart fonksiyon çağrı yığınlarını takip eder
Özel Uygulama Zayıf Yönleri
- Bakım yükü: Ekip hataları, uç durumları ve güvenlik yamalarını sahiplenir
- Sınırlı topluluk testi: Olağandışı uç durumlar üretime kadar ortaya çıkmayabilir
- Özellik yeniden uygulaması: Alan izinleri, DB sorgu dönüşümü, koşul operatörleri (
$in,$ne,$gte) -- kütüphanelerin zaten sağladığını yeniden oluşturma - Onboarding maliyeti: Yeni ekip üyeleri belgelenmiş bir kütüphane yerine özel bir API öğrenir
Kütüphane Güçlü Yönleri
- Topluluk tarafından test edilmiş: Binlerce proje, keşfedilen ve düzeltilen uç durumlar
- Yerleşik özellikler: Alan izinleri, MongoDB tarzı koşullar, Prisma/Mongoose adaptörleri
- Dokümantasyon ve topluluk: Öğreticiler, Stack Overflow yanıtları, konferans sunumları
- Azaltılmış bakım: Güvenlik yamaları ve özellik eklemeleri bakımcılar tarafından yönetilir
Kütüphane Zayıf Yönleri
- API kısıtlamaları: Kütüphanenin API'si serinin
can()imzasıyla eşleşmeyebilir - Bağımlılık riski: Kütüphane bakımı yavaşlayabilir veya durabilir
- Entegrasyon sürtünmesi: Class tabanlı kütüphaneler React Server Components ile çakışır
- Kara kutu davranışı: İzin reddini debug etmek kütüphane iç yapısını anlamayı gerektirir
Build vs. Kütüphane Karar Çerçevesi
In-Code vs. DSL Tabanlı Yaklaşımlar
CASL Entegrasyonu
Bu Seri İçin Neden CASL
CASL, en popüler JavaScript/TypeScript yetkilendirme kütüphanesidir (~6KB çekirdek). İzomorfiktir (sunucu ve istemcide çalışır), ABAC koşullarını, alan düzeyinde izinleri ve veritabanı sorgu dönüşümünü destekler. Seri zaten CASL'ın sağladığı her şeyi oluşturduğundan, doğrudan özellik bazında karşılaştırma mümkündür.
Migration: AbilityBuilder
Post 104'teki özel PermissionBuilder, CASL'ın AbilityBuilder'ına eşlenir:
Özel (Post 104-105):
CASL karşılığı:
Temel API farklılıkları:
- Koşullar fonksiyonlar yerine MongoDB tarzı nesnelerdir (
{ authorId: user.userId }) - Roller için builder-pattern zincirleme yok -- kullanıcı rolünde
if/elsedallanma kullanılır - Negatif kurallar için
cannot()(CASL'a özel -- özel sistemde bu yoktu) 'manage'tüm CRUD aksiyonları için CASL'ın joker karakteri;'all'tüm subject'ler için
subject() Helper'ı ve Sürtünmesi
CASL, kontrol edilen nesnenin türünü bilmelidir. Class'larla bu otomatiktir (class adı üzerinden). TypeScript uygulamalarının genellikle kullandığı düz nesnelerde ise subject() helper'ı gereklidir:
Geçici çözüm 1: Object spreading
Geçici çözüm 2: Özel detectSubjectType
Geçici çözüm 3: Lambda matcher ile PureAbility (RSC uyumlu)
Tip:
PureAbility+ lambda matcher yaklaşımı en RSC-uyumlu seçenektir, ancak CASL'ın MongoDB tarzı sorgu operatörlerini ve Prisma entegrasyonunu kaybeder. CASL'ın tam özellik seti ile modern React uyumluluğu arasında gerçek bir ödünleşim vardır.
CASL'da Tenant İzolasyonu
Özel sistemde her kurala otomatik olarak uygulanan bir global() koşulu vardı. CASL, tenantId'yi her kurala ayrı ayrı eklemeyi gerektirir. Bir kuralda eksik bırakmak çapraz tenant sızıntısı oluşturur. Bu önemli bir ergonomik farktır.
CASL Alan ve Veritabanı Entegrasyonu
permittedFieldsOf ile Alan Düzeyinde İzinler
Post 105'teki özel getVisibleFields() ile karşılaştırın -- kavram aynı, API farklı. CASL, bir kuralda alan kısıtlaması olmadığında tüm olası alanları döndüren bir fieldsFrom callback'i gerektirir.
CASL AST'den Prisma Sorgu Dönüşümü
Post 105'teki özel toWhereClause() ile karşılaştırma:
- CASL'ın
accessibleBy()fonksiyonu MongoDB tarzı koşulları Prismawheresözdizimine dönüştürür - Özel
toWhereClause(),toFiltercallback'leri olan koşul tanımlayıcılarını kullanır - CASL, birden fazla eşleşen kural arasında
ORmantığını otomatik olarak yönetir - CASL, hiçbir kural eşleşmezse
ForbiddenErrorfırlatır (fail-closed)
Warning:
accessibleBy()yalnızca MongoDB tarzı koşullarla (createMongoAbility'den) çalışır, lambda koşullarıyla (PureAbility) çalışmaz. RSC uyumluPureAbilitypattern'ini kullanıyorsanız, Prisma sorgu dönüşümünü kaybedersiniz. Bu, mevcut CASL mimarisinde kesin bir ödünleşimdir.
Kapsamlı Karşılaştırma
RBAC vs. Özel ABAC vs. CASL ABAC
Karar Çerçevesi: Hangi Sistemi Seçmelisiniz?
Her Birini Ne Zaman Seçmeli
RBAC (Post 103) -- Varsayılan Seçim
- Ekip: Her boyutta
- Uygulama: Dahili araçlar, basit SaaS, net rollere sahip içerik platformları
- Karmaşıklık: Düşük -- 2-4 rol, izinler yalnızca role bağlı
- Seçim sinyali: İzin gereksinimleri "bu rol bu şeyleri yapabilir" şeklinde temiz bir eşleme oluşturur
- Yükseltme sinyali:
can()yanında helper fonksiyonlar çoğalmaya başlar
Özel ABAC (Post 104-105) -- Tam Kontrol
- Ekip: Yetkilendirme uzmanlığı var, auth kodunu sürdürmeye istekli
- Uygulama: Karmaşık iş kurallarına sahip SaaS, alan düzeyinde görünürlük, büyük veri kümeleri
- Karmaşıklık: Yüksek -- sahiplik, departman, durum, zaman koşulları
- Seçim sinyali:
can()kaynak başına 3+ bağlamsal koşulu değerlendirmeli - Yükseltme sinyali: Auth bakımı için ekip bant genişliği azalır; birden fazla ORM'de DB sorgu adaptörleri gerekir
CASL ABAC (Bu Yazı) -- Topluluk Tarafından Test Edilmiş Kütüphane
- Ekip: Auth iç yapısı yerine iş mantığına odaklanmak istiyor
- Uygulama: Prisma/MongoDB kullanan SaaS, alan izinleri ve DB filtreleme gerektiren
- Karmaşıklık: Yüksek -- ama ekip özel kod yerine kütüphane API'sini tercih ediyor
- Seçim sinyali: Özel ABAC özellik seti CASL'ın yetenekleriyle eşleşiyor
- Kaçınma sinyali: Düz nesnelerle yoğun RSC kullanımı; çevre koşulları gereksinimi; global tenant izolasyonu gereksinimi
Harici PDP (Cerbos, OPA, Cedar) -- Servis Olarak Yetkilendirme
- Ekip: Adanmış platform/güvenlik ekibi
- Uygulama: Mikroservisler, polyglot stack, servisler arası paylaşılan yetkilendirme kararları
- Karmaşıklık: Çok yüksek -- birden fazla servis tutarlı yetkilendirme gerektirir
- Seçim sinyali: Birden fazla backend aynı yetkilendirme kararlarına ihtiyaç duyar; uyumluluk ayrıştırılmış, denetlenebilir politika yönetimi gerektirir
Yaygın Tuzaklar
-
Bir CASL kuralında tenant izolasyonunu unutma: CASL'da global koşul yoktur. Bir kuralda
tenantIdeksik bırakmak çapraz tenant sızıntısı oluşturur. Her platform-admin olmayan kuralıntenantIdiçerdiğini doğrulayan bir lint kuralı veya birim testi yazın. -
CASL'ın RSC ile sorunsuz çalıştığını varsaymak:
subject()helper'ı nesneleri mutasyona uğratır. React Server Components serileştirilebilir veri gerektirir. CASL Entegrasyonu bölümündeki üç geçici çözümden birini kullanın. -
PureAbility Prisma entegrasyonunu kaybeder: RSC uyumlu
PureAbility+ lambda matcher pattern'i koşulları Prismawherekoşullarına dönüştüremez. Ekipler RSC uyumluluğu ile DB sorgu filtreleme arasında seçim yapmalıdır. -
Erken aşırı mühendislik: RBAC başarısız olmadan ABAC'a veya CASL'a geçmek erkendir. Serinin ilerleyişi gerçek dünya evrimini yansıtır: basit başlayın, mevcut sınırlamalar ortaya çıktığında karmaşıklık ekleyin.
-
Multi-tenancy'yi sonradan düşünme: Şema oluşturulduktan sonra her tabloya
tenant_ideklemek zorlu bir migration'dır. İlk sürüm yalnızca bir tenant'a sahip olsa bile, tenant izolasyonunu baştan tasarlayın. -
Platform yöneticisini tenant yöneticisi ile karıştırma: Platform yöneticileri tüm tenant'ları yönetir (çapraz tenant erişimi). Tenant yöneticileri yalnızca kendi tenant'larını yönetir. Bu rolleri karıştırmak ya aşırı izin verici tenant yöneticileri ya da yetersiz izin verici platform yöneticileri oluşturur.
-
Harici PDP'yi çok erken seçme: Cerbos, OPA ve Cedar altyapı karmaşıklığı ekler. Monolitik bir Next.js uygulaması için süreç içi yetkilendirme (özel veya CASL) daha basit ve hızlıdır. Harici PDP'ler, yetkilendirme kararlarının bağımsız olarak dağıtılan servisler arasında paylaşılması gerektiğinde anlam kazanır.
-
Çapraz tenant senaryolarını test etmeme: Birim testleri genellikle tek bir tenant ID kullanır. Kullanıcı A'nın (tenant 1) Kullanıcı B'nin (tenant 2) dokümanına erişmeye çalıştığı açık test senaryoları ekleyin. Bu testler eksik tenant filtrelerini yakalar.
Seri Retrospektifi
Yedi Hedef Karnesi
Post 101 herhangi bir izin sistemi için yedi hedef belirledi. Her yaklaşımın puanlaması:
Seri Mimari Evrimi
Altı yazı boyunca temel içgörü: Post 102'deki service layer hiçbir zaman değişmez. Her yetkilendirme yaklaşımı için zorunlu kılma noktasıdır. İçindeki karar motoru basit rol kontrollerinden RBAC'a, ABAC'a ve CASL'a evrilir, ama mimari sabit kalır. İlerlemeli yaklaşımı çalıştıran budur -- her yükseltme service layer içinde kapsanır.
Mikroservis Yetkilendirmesi: İleriye Bakış
Seri monolitik bir Next.js uygulamasına odaklandı. Uygulamalar büyüdükçe, yetkilendirme kararları servis sınırları arasında çalışmalıdır. Üç pattern ortaya çıkar:
Pattern 1: Merkezi Yetkilendirme Servisi
Tek bir servis tüm izin kararlarını değerlendirir. Diğer servisler gRPC/HTTP ile çağrır. Tek doğruluk kaynağı, ama tek hata noktası ve her istekte ağ gecikmesi.
Pattern 2: Gömülü PDP (Sidecar)
Her mikroservis kendi policy engine'ini çalıştırır (OPA sidecar, Cerbos sidecar). Politikalar merkezi olarak yönetilir ve tüm sidecar'lara dağıtılır. Kararlar için ağ atlaması yok, ama politika senkronizasyonu karmaşıklığı ve sürüm kayması riski var.
Pattern 3: Token Tabanlı Claim'ler
Yetkilendirme verileri JWT claim'lerine gömülür (roller, izinler, tenantId). Servisler ek politika kontrolü olmadan token'a güvenir. En basit altyapı, ama eskimiş claim'ler ve kaynak düzeyinde yetkilendirme yok.
Monolitten mikroservislere geçen ekipler için: servisler arası auth için Pattern 3 (token claim'ler) ile başlayın, servisler arasında ayrıntılı kaynak düzeyinde yetkilendirme gerektiğinde Pattern 2 (gömülü PDP) ekleyin.
İzin Depolama: Kod vs. Veritabanı
Hibrit pattern üretim SaaS için iyi çalışır: varsayılan izin setini kodda tanımlayın (type-safe, test edilmiş), tenant'ların belirli kuralları veritabanı tablosu ile geçersiz kılmasına izin verin. can() fonksiyonu önce kod tabanlı kuralları kontrol eder, sonra veritabanı geçersiz kılmalarını uygular.
Seri Özeti
Altı yazı boyunca izin sistemi, dağınık if ifadelerinden üretim kalitesinde bir yetkilendirme mimarisine evrildi:
- Post 101: Sorunu belirledi -- dağınık kontroller, tutarsız zorunlu kılma, fail-closed varsayılanı yok
- Post 102: Mimariyi oluşturdu -- tek zorunlu kılma noktası olarak service layer
- Post 103: İlk karar motorunu ekledi -- generic constraint'lerle type-safe RBAC
- Post 104: Rol tabanlı aramayı attribute tabanlı politikalarla değiştirdi -- sahiplik, departman, durum koşulları
- Post 105: ABAC'ı çevre kuralları, alan düzeyinde izinler ve veritabanı sorgu filtreleme ile genişletti
- Post 106 (bu yazı): Multi-tenancy ekledi, CASL'ı bir kütüphane alternatifi olarak değerlendirdi ve kesin karar çerçevesini sağladı
Service layer tek sabittir. İçindeki karar motoru RBAC, özel ABAC, CASL veya harici PDP olabilir. Post 102'deki mimari, yapısal değişiklik olmadan bunların hepsini destekler. Hedef başından beri buydu.
Kaynaklar
- CASL - Isomorphic Authorization JavaScript Library - Bu yazıda değerlendirilen temel yetkilendirme kütüphanesi, ABAC koşulları, alan düzeyinde izinler ve veritabanı sorgu dönüşümü desteği ile
- CASL v6 - Prisma Integration - CASL kurallarını Prisma
wherekoşullarına dönüştürenaccessibleBy()fonksiyonu için resmi dokümantasyon - CASL v6 - Restricting Fields Access -
permittedFieldsOf()ve kural tanımlarındaki alan dizileri hakkında resmi CASL dokümantasyonu - Shipping Multi-Tenant SaaS Using PostgreSQL Row-Level Security (Nile) - Tenant bağlam yayılımı ve fail-secure varsayılanları dahil multi-tenant SaaS için RLS uygulama rehberi
- The Developer's Guide to SaaS Multi-Tenant Architecture (WorkOS) - İzolasyon gereksinimlerine ve tenant sayısına dayalı karar kriterleriyle multi-tenancy modellerine mimari genel bakış
- How to Choose the Right Authorization Model for Your SaaS (WorkOS) - Roller, izinler, ABAC, ReBAC ve politika tabanlı yetkilendirme arasında seçim yapmak için karar çerçevesi
- Policy Engines: OPA vs Cedar vs Zanzibar (Permit.io) - OPA (Rego tabanlı), Cedar (AWS, resmi doğrulama) ve Zanzibar (Google, graf tabanlı ReBAC) karşılaştırma analizi
- 3 Most Common Authorization Designs for SaaS Products (Cerbos) - ACL, RBAC ve ABAC pattern'lerinin her birinin ne zaman kullanılacağına dair rehberle karşılaştırması
- Multi-Tenant Data Isolation with PostgreSQL Row Level Security (AWS) - PostgreSQL'de tenant izolasyonu için RLS uygulama AWS rehberi
- Best Practices for Authorization in Microservices (Permit.io) - Merkezi ve gömülü PDP pattern'lerini ve önerilen sidecar mimarisini kapsayan rehber
- RBAC vs ABAC: Main Differences and When to Use Each (Oso) - Hibrit yaklaşımlar için karar kriterleriyle RBAC ve ABAC modellerinin karşılaştırması
- OWASP Microservices Security Cheat Sheet - Merkezi PDP ve gömülü PDP sidecar dahil mikroservis yetkilendirme pattern'leri hakkında OWASP rehberi
- An Introduction to Google Zanzibar and ReBAC (Authzed) - Google Zanzibar'ın ilişki tabanlı erişim kontrol modeli ve ölçekte yetkilendirmeyi nasıl desteklediğine genel bakış
Ölçeklenebilir İzin Sistemleri
TypeScript ve Next.js ile ölçeklenebilir izin sistemleri oluşturma rehberi. Basit kontrol mekanizmalarından RBAC ve ABAC'a, oradan multi-tenant yetkilendirme sistemlerine kadar kapsamlı bir seri.