Ana içeriğe geç

Tenant Izolasyon Sorunlari

Multi-tenant mimaride kiraci (tenant) verilerinin birbirine karismasi, yetkisiz erisim ve capraz veri sizintisi sorunlarinin teshis ve cozum rehberi. Zeus 2.0'da tenant izolasyonu API, veritabani, MQTT ve WebSocket katmanlarinda zorunludur.

Sorun Tablosu

SemptomOlasi SebepKontrolCozum
Baska tenant'in verisi gorunuyorQuery'de tenant_id filtresi eksikSQL loglarini kontrol etEndpoint'e tenant dependency ekle
MQTT'de capraz veriTopic'te tenant prefix eksikEMQX ACL kontrolACL kurali ekle
WebSocket'te yanlis veriRoom izolasyonu bozukWebSocket hub loglariTenantRoom mantigini kontrol et
API'de 403 hatasiYanlis tenant_id, role yetersizJWT token decode etDogru tenant ile login, role guncelle

Detayli Teshis ve Cozumler

1. Baska Tenant'in Verisi Gorunuyor

Semptom: Kullanici dashboard'unda kendi santraline ait olmayan cihazlar veya olcum verileri gorunuyor. En kritik izolasyon ihlali.

Olasi Sebepler:

  • Yeni eklenen endpoint'te get_current_tenant dependency'si unutulmus
  • SQLAlchemy query'sinde .filter(Model.tenant_id == tenant_id) eksik
  • Service katmaninda tenant filtresi bypass edilmis
  • JOIN sorgularinda tenant filtresi sadece ana tabloya uygulanmis, iliskili tabloya uygulanmamis

Kontrol Adimlari:

# 1. Sorunlu endpoint'i belirle — hangi sayfada yanlis veri gorunuyor?

# 2. Ilgili router dosyasini kontrol et:
# backend/app/features/{module}/router.py

# Dogru pattern (tenant dependency var):
@router.get("/devices")
async def list_devices(
tenant_id: int = Depends(get_current_tenant), # ← BU OLMALI
db: AsyncSession = Depends(get_db)
):
devices = await device_service.get_all(db, tenant_id=tenant_id)
return devices

# Hatali pattern (tenant filtresi yok):
@router.get("/devices")
async def list_devices(
db: AsyncSession = Depends(get_db)
):
devices = await device_service.get_all(db) # ← TENANT FILTRESI YOK!
return devices
-- 3. SQL loglarinda tenant filtresi var mi kontrol et
-- PostgreSQL'de sorgu loglama acik olmali (log_min_duration_statement = 0)

-- Dogru sorgu:
SELECT * FROM devices WHERE tenant_id = 5 AND is_active = true;

-- Hatali sorgu (tenant filtresi yok):
SELECT * FROM devices WHERE is_active = true;
-- ↑ TUM TENANT'LARIN CIHAZLARINI DONDURUR!

Cozum:

  1. Dependency ekleme: Router fonksiyonuna tenant_id: int = Depends(get_current_tenant) parametresi ekleyin
  2. Service katmani: Service fonksiyonlarinda tenant_id parametresini zorunlu yapin
  3. Query filtresi: Tum SQLAlchemy sorgularina .filter(Model.tenant_id == tenant_id) ekleyin
  4. JOIN kontrol: Birden fazla tablo iceren sorgularda iliskili tablolarda da tenant filtresi uygulayin
  5. Audit: code-audit-sentinel ile tum endpoint'leri tarayarak tenant filtresi eksik olanlari tespit edin
Kritik

Tenant izolasyon ihlali en yuksek oncelikli guvenlik acigi olarak degerlendirilmelidir. Tespit edildiginde derhal duzeltilmeli ve diger endpoint'ler de taranmalidir.


2. MQTT'de Capraz Veri

Semptom: Bir tenant'in MQTT subscriber'i baska bir tenant'a ait cihaz verilerini aliyor. Dashboard'da kendi cihazi olmayan veriler gozuyor.

Olasi Sebepler:

  • MQTT topic'inde tenant prefix'i kullanilmamis
  • EMQX ACL kurali eksik veya yanlis yapılandırılmis
  • Wildcard subscription cok genis (#) kullanilmis
  • Gateway konfigurasyonunda yanlis tenant_id atanmis

Kontrol Adimlari:

# 1. MQTT topic yapisi dogru mu kontrol et
# Dogru pattern: zeus/{tenant_id}/{gateway_id}/{device_id}/{data_type}
# Hatali pattern: zeus/{gateway_id}/{device_id}/{data_type} ← tenant_id YOK

# 2. EMQX ACL kurallarini kontrol et
docker compose exec emqx cat /etc/emqx/acl.conf

# 3. Aktif subscription'lari listele
docker compose exec emqx emqx_ctl subscriptions list

# 4. Belirli bir topic'e gelen mesajlari izle
mosquitto_sub -h localhost -p 1883 -t "zeus/+/+/+/telemetry" -v
# ↑ Her mesajda topic'teki tenant_id'yi kontrol et
# Beklenen ACL kurali ornegi:
# {allow, {user, "gw_{gateway_id}"}, publish, ["zeus/{tenant_id}/{gateway_id}/#"]}.
# {allow, {user, "app_{user_id}"}, subscribe, ["zeus/{tenant_id}/#"]}.
# {deny, all}.

Cozum:

  1. Topic duzeltme: Tum MQTT topic'lerinde zeus/{tenant_id}/ prefix'ini zorunlu kilin
  2. ACL guncel leme: EMQX ACL kurallarini tenant bazli kisitlama ile guncelleyin
  3. Wildcard kisitlama: Kullanici subscription'larinda zeus/{tenant_id}/# kullanin, zeus/# kullanmayin
  4. Gateway config: Gateway konfigurasyonunda dogru tenant_id atandigindan emin olun
  5. ACL yeniden yukleme: docker compose exec emqx emqx_ctl acl reload

3. WebSocket'te Yanlis Veri

Semptom: WebSocket uzerinden gelen gercek zamanli guncellemelerde baska tenant'a ait veriler gorunuyor.

Olasi Sebepler:

  • WebSocket room (oda) izolasyonu tenant bazli degil
  • Room isimlendirmesinde tenant_id bulunmuyor
  • Broadcast mesaji tum room'lara gonderiliyor

Kontrol Adimlari:

# 1. WebSocket hub kodunu kontrol et
# backend/app/core/websocket/

# Dogru room pattern:
room_name = f"tenant_{tenant_id}_dashboard"
# veya
room_name = f"tenant_{tenant_id}_device_{device_id}"

# Hatali room pattern:
room_name = f"dashboard" # ← TENANT AYIRIMI YOK!
room_name = f"device_{device_id}" # ← TENANT AYIRIMI YOK!
# 2. Broadcast fonksiyonunu kontrol et
# Dogru: sadece tenant room'una gonder
await ws_manager.broadcast_to_room(
room=f"tenant_{tenant_id}_dashboard",
message=data
)

# Hatali: tum bagli istemcilere gonder
await ws_manager.broadcast(message=data) # ← HERKES GORUYOR!

Cozum:

  1. Room isimlendirme: Tum WebSocket room isimlerinde tenant_{tenant_id}_ prefix'i kullanin
  2. Baglanti dogrulama: WebSocket baglantisi kurulurken JWT'den tenant_id cikararak dogrulayin
  3. Broadcast kisitlama: broadcast() yerine broadcast_to_room() kullanarak sadece ilgili tenant room'una gonderin
  4. Baglanti reddi: Gecersiz veya eksik tenant bilgisi olan WebSocket baglantilari reddedilmelidir

4. API'de 403 Hatasi

Semptom: Kullanici gecerli bir token ile istek atiyor ancak 403 Forbidden hatasi aliyor.

Olasi Sebepler:

  • Kullanicinin token'indeki tenant_id ile istek yapilan kaynaktaki tenant_id uyusmuyyor
  • Kullanicinin rolu (role) istenen islem icin yetki vermiyor
  • Token suresi dolmus ve yenilenmemis (401 yerine 403 donuyor olabilir)
  • Coklu tenant ortaminda yanlis tenant ile oturum acilmis

Kontrol Adimlari:

# 1. JWT token'i decode et
# https://jwt.io adresinde veya komut satirinda:
echo "eyJ..." | base64 -d | python3 -m json.tool

# Token payload'inda kontrol et:
# {
# "sub": 42, ← user_id
# "tenant_id": 5, ← hangi tenant?
# "role": "operator", ← hangi rol?
# "exp": 1735689600 ← suresi dolmus mu?
# }
# 2. Endpoint'teki yetki kontrolunu incele
@router.delete("/devices/{device_id}")
async def delete_device(
device_id: int,
tenant_id: int = Depends(get_current_tenant),
current_user: User = Depends(get_current_user),
):
# Kullanicinin rolu admin veya manager mi?
if current_user.role not in ["admin", "manager"]:
raise HTTPException(status_code=403, detail="Yetkisiz islem")

# Cihaz bu tenant'a ait mi?
device = await device_service.get(db, device_id)
if device.tenant_id != tenant_id:
raise HTTPException(status_code=403, detail="Erisim engellendi")

Cozum:

  1. Dogru tenant ile login: Kullanicinin dogru tenant'a ait hesap ile giris yaptigindan emin olun
  2. Rol guncelleme: Gerekli islem icin uygun rolu (admin, manager, operator, viewer) atayın
  3. Token yenileme: Suresi dolmus token varsa yeniden login yaptirin
  4. Kaynak tenant'i: Erişilmek istenen kaynagin (cihaz, alarm vb.) kullanicinin tenant'ina ait oldugundan emin olun

Tenant Izolasyon Kontrol Listesi

Yeni bir endpoint veya ozellik eklerken su kontrol listesini uygulayın:

  • Router fonksiyonunda Depends(get_current_tenant) var mi?
  • Service fonksiyonunda tenant_id parametresi zorunlu mu?
  • SQLAlchemy query'lerinde .filter(tenant_id == ...) var mi?
  • JOIN sorgularinda tum tablolarda tenant filtresi var mi?
  • MQTT topic'inde {tenant_id} prefix'i var mi?
  • WebSocket room'unda tenant_{tenant_id}_ prefix'i var mi?
  • EMQX ACL kurali tenant bazli kisitlama yapiyor mu?
  • API response'unda baska tenant'a ait veri siziyor mu? (test edin!)