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
| Semptom | Olasi Sebep | Kontrol | Cozum |
|---|---|---|---|
| Baska tenant'in verisi gorunuyor | Query'de tenant_id filtresi eksik | SQL loglarini kontrol et | Endpoint'e tenant dependency ekle |
| MQTT'de capraz veri | Topic'te tenant prefix eksik | EMQX ACL kontrol | ACL kurali ekle |
| WebSocket'te yanlis veri | Room izolasyonu bozuk | WebSocket hub loglari | TenantRoom mantigini kontrol et |
| API'de 403 hatasi | Yanlis tenant_id, role yetersiz | JWT token decode et | Dogru 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_tenantdependency'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:
- Dependency ekleme: Router fonksiyonuna
tenant_id: int = Depends(get_current_tenant)parametresi ekleyin - Service katmani: Service fonksiyonlarinda
tenant_idparametresini zorunlu yapin - Query filtresi: Tum SQLAlchemy sorgularina
.filter(Model.tenant_id == tenant_id)ekleyin - JOIN kontrol: Birden fazla tablo iceren sorgularda iliskili tablolarda da tenant filtresi uygulayin
- Audit:
code-audit-sentinelile tum endpoint'leri tarayarak tenant filtresi eksik olanlari tespit edin
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:
- Topic duzeltme: Tum MQTT topic'lerinde
zeus/{tenant_id}/prefix'ini zorunlu kilin - ACL guncel leme: EMQX ACL kurallarini tenant bazli kisitlama ile guncelleyin
- Wildcard kisitlama: Kullanici subscription'larinda
zeus/{tenant_id}/#kullanin,zeus/#kullanmayin - Gateway config: Gateway konfigurasyonunda dogru
tenant_idatandigindan emin olun - 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:
- Room isimlendirme: Tum WebSocket room isimlerinde
tenant_{tenant_id}_prefix'i kullanin - Baglanti dogrulama: WebSocket baglantisi kurulurken JWT'den
tenant_idcikararak dogrulayin - Broadcast kisitlama:
broadcast()yerinebroadcast_to_room()kullanarak sadece ilgili tenant room'una gonderin - 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_idile istek yapilan kaynaktakitenant_iduyusmuyyor - 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:
- Dogru tenant ile login: Kullanicinin dogru tenant'a ait hesap ile giris yaptigindan emin olun
- Rol guncelleme: Gerekli islem icin uygun rolu (admin, manager, operator, viewer) atayın
- Token yenileme: Suresi dolmus token varsa yeniden login yaptirin
- 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_idparametresi 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!)