Çok kiracılı uygulamalarda veri izolasyonu: tenant bağlamını sınır olarak tasarlamak
Çok kiracılı bir uygulamada en tehlikeli hata çoğu zaman karmaşık bir saldırı değildir. Bir sorguda eksik kalan tenant_id filtresi, bir önbellek anahtarında unutulan tenant bölümü veya arka plan işine taşınmayan bağlam; bir kurumun verisini başka bir kurumun görmesine yol açabilir. Bu tür hatalar nadiren yalnızca bir veritabanı sorunudur. Kimlik doğrulama, yetkilendirme, veri erişimi, dosya depolama, arama ve gözlemlenebilirlik boyunca taşınan bir sınır sorunudur.
Sağlam tasarımın ilkesi basittir: Tenant, isteğe sonradan eklenen bir filtre değil; her iş akışının zorunlu güvenlik bağlamıdır. Bir kullanıcı oturum açtığında hangi tenant adına hareket ettiği belirlenir; bu bilgi güvenilir bir kaynaktan türetilir ve sistemdeki her erişim kararına eşlik eder.
Tenant bağlamını kimlikten türetin
İstemcinin gönderdiği X-Tenant-Id başlığını tek başına güvenilir kabul etmek doğru değildir. Kullanıcı başka bir tenant kimliği deneyebilir veya eski bir istemci yanlış değer gönderebilir. Tenant seçimi, doğrulanmış kimlik, üyelik kaydı, aktif organizasyon seçimi ve yetki kurallarıyla sunucuda çözülmelidir.
Bazı ürünlerde bir kullanıcının birden fazla organizasyona üyeliği olabilir. Bu durumda aktif tenant seçimi kullanıcı deneyiminin parçasıdır, ancak nihai karar yine sunucudadır. Sunucu; kullanıcının seçtiği tenant'a üye olduğunu, rolünün hedef işlemi yapmaya yettiğini ve gerekirse abonelik ya da politika koşullarının sağlandığını doğrular.
Bağlam oluştuğunda onu yalnızca controller katmanında değişken olarak tutmak yeterli değildir. İstek kapsamındaki servisler, veri erişim katmanı, audit kaydı ve çağrılan diğer servisler aynı bağlamı okuyabilmelidir. Buna karşılık tenant bağlamını global, değiştirilebilir bir değişkende saklamak eşzamanlı isteklerde karışma riski yaratır. İstek kapsamına bağlı ve açıkça geçirilen bir taşıma modeli daha güvenlidir.
Veri modelinde sahipliği görünür kılın
Paylaşımlı veritabanı ve paylaşımlı şema yaklaşımında tenant'a ait her satır, açık bir tenant_id taşımalıdır. Bu alan yalnızca performans için eklenmiş ikincil bir kolon olmamalı; yabancı anahtarlar, benzersiz kısıtlar ve indekslerde iş anlamının parçası olmalıdır.
Örneğin bir kullanıcı e-posta adresi kurum içinde benzersizse, tek başına email için benzersiz indeks yerine (tenant_id, email) benzersizliği gerekir. Bir belgeye ait alt kaydın tenant kimliğini yalnızca üst tablodan varsaymak yerine, sorgu desenine ve bütünlük ihtiyacına göre doğrudan taşımak yararlı olabilir. Böylece hem filtreleme hem de yanlış ilişki kurma hataları daha görünür hâle gelir.
Tenant dışı ortak veri ile tenant verisini ayırmak da önemlidir. Sistem şablonları, genel ülke listeleri veya ürün tanımları her tenant'a ait olmayabilir. Bu kayıtların açıkça global olarak modellenmesi, “null tenant” gibi belirsiz anlamlı istisnaların yayılmasını engeller.
Sorgu filtresini varsayılan, kısıtı son savunma yapın
Uygulama katmanında tüm repository veya sorguların tenant filtresini varsayılan olarak eklemesi gerekir. Geliştiricinin her sorguda hatırlamasına bırakılan güvenlik kuralı zamanla ihlal edilir. Sorgu yardımcıları, repository arayüzleri veya ORM düzeyinde global filtreler bu riski azaltabilir.
Yine de uygulama filtresi tek savunma olmamalıdır. Veritabanının desteklediği durumlarda row-level security gibi politikalar, yanlış veya eksik bir sorgunun bile yalnızca geçerli tenant satırlarını döndürmesini sağlayabilir. Bu politikalar dikkatle devreye alınmalıdır: bağlantının hangi tenant bağlamıyla açıldığı, migration ve yönetim işlemlerinin nasıl çalıştığı, performans etkisi ve acil durum erişimi açıkça tasarlanmalıdır.
Amaç her katmanda aynı kuralı kopyalamak değil, hata yapıldığında etkisini sınırlayan bağımsız korumalar oluşturmaktır. Bir servis hatalı sorgu yazsa bile veritabanı politikası sızıntıyı engelleyebiliyorsa güvenlik sınırı güçlenir.
Arka plan işlerinde bağlamı iş yüküyle taşıyın
HTTP isteği bittiğinde request scope kaybolur. E-posta, rapor, indeksleme veya bildirim işi daha sonra çalışacaksa tenant kimliği iş yükünün açık bir parçası olmalıdır. Worker, mesajdaki tenant kimliğini güvenli biçimde doğrulayıp kendi erişim bağlamını kurar; önceki bir işten kalan bağlamı asla yeniden kullanmaz.
Kuyruk mesajlarına yalnızca tenant kimliği değil, işlem için gerekli en az bilgi konmalıdır. Büyük veya hassas nesneleri doğrudan mesaj gövdesine koymak yerine, tenant sınırıyla birlikte güvenli bir kayıt kimliği taşımak daha iyi olabilir. Worker kayda erişirken yine tenant filtresi ve yetki politikası uygular.
Bu kural tekrar deneme için de önemlidir. İş yeniden teslim edildiğinde aynı tenant bağlamıyla idempotent şekilde çalışmalı; farklı bir tenant'ın kaydıyla eşleşme ihtimali olmamalıdır.
Önbellek, dosya ve arama indeksini unutmayın
Veritabanı sorguları doğru filtrelendiği hâlde veri sızıntıları önbellek katmanından çıkabilir. user:123 gibi bir cache anahtarı farklı tenant'larda aynı yerel kimlik için çakışabilir. Anahtarlar tenant alanını içermeli; invalidation ve TTL stratejileri de tenant etkisini hesaba katmalıdır.
Nesne depolamada dosya yolları ve imzalı URL üretimi tenant'a bağlı olmalıdır. Bir dosya kimliğini bilmek, başka tenant'a ait nesneyi indirmeye yetmemelidir. Arama indeksinde de her belge tenant kimliğiyle etiketlenmeli ve sorgu tarafında zorunlu filtre kullanılmalıdır. İndeksleme gecikmesi veya silme olayı, erişim kontrolünü geçici olarak devre dışı bırakmamalıdır.
Bu bağımlılıklar için ortak bir kontrol listesi faydalıdır: “Bu veri hangi tenant'a ait? Anahtar, sorgu, erişim izni ve silme akışı bu bilgiyi taşıyor mu?”
Yönetim, log ve analitiği ayrı ele alın
Platform yöneticilerinin çoklu tenant görmesi gerekebilir. Bu ihtiyaç, normal kullanıcı bağlamını atlamanın gerekçesi olmamalıdır. Yönetim erişimi için ayrı rol, açık kapsam, gerekçe ve denetim kaydı tasarlamak gerekir. Mümkünse geniş erişim zaman sınırlı, onaylı ve görünür olmalıdır.
Log, trace ve metric etiketlerinde tenant kimliği teşhis için yararlı olabilir; ancak bu kimlik hassas ya da yüksek kardinaliteli olabilir. Takma ad, sınırlı hash veya yalnızca hata kaydında erişilebilir bağlam kullanmak daha güvenlidir. Loglara müşteri içeriği, erişim belirteci veya özel belge metni yazılmamalıdır.
Analitik sorgularında da tenant ayrımı korunmalıdır. Toplu rapor gerçekten platform geneli olmalıysa, bu erişim ürün politikasıyla açıkça tanımlanmalıdır; “raporlama için filtreyi kaldır” gizli bir çözüm olmamalıdır.
İzolasyonu test senaryosu yapın
Tenant izolasyonu yalnızca kod incelemesinde kontrol edilecek bir ilke değildir. Otomatik testlerde en az iki tenant oluşturup aynı isimli veya benzer kimlikli kayıtlarla çalışmak gerekir. Tenant A kullanıcısı Tenant B'nin listesini, ayrıntısını, dosyasını, önbellek sonucunu, arama kaydını veya arka plan iş sonucunu görebiliyor mu? Yanıt her kanalda sistematik olarak “hayır” olmalıdır.
Bu testler yetki reddi, doğrudan kimlik tahmini, pagination, dışa aktarma ve yönetici rolleri gibi yolları da kapsamalıdır. Üretimde ise tenant dışı erişim denemeleri ve beklenmeyen boş filtreler alarm üretecek şekilde izlenmelidir.
Çok kiracılı izolasyon, tek bir middleware veya SQL koşulu değildir. Tenant bağlamını kimlikten türetip veri, iş, depolama ve gözlemlenebilirlik zincirinde koruyan sistemler; büyüdükçe güvenliği geriye dönük bir düzeltme olmaktan çıkarır ve ürün mimarisinin doğal bir parçası hâline getirir.