# Prod erişimi tek kişideydi, o kişi izne çıktı

> Üretim veritabanına erişimi olan tek kişi tatile gidince geliştirme durdu. İhtiyaç dört cümleye indi; her biri bir tasarım kararına dönüştü.

- Yayın: 2026-09-14
- Kategori: Araçlar & Teknolojiler
- Etiketler: Güvenlik, Veritabanı, Laravel
- Okuma süresi: 4 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/prod-erisimi-tek-kisideydi-queryproxy/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Ekipte üretim veritabanına erişimi olan tek kişi vardı. Bu, uzun süre bir sorun
gibi görünmedi — ta ki o kişi izne çıkana kadar. Bir hafta içinde şunu fark
ettik: bug'lar tespit edilemiyordu. Kod okunabiliyordu, testler koşuyordu,
hipotezler kuruluyordu; ama hiçbiri "üretimde bu kaydın hâli ne" sorusunun
yerine geçmiyordu. Geliştirme durmadı, ama teşhis durdu — ve teşhis durunca
geri kalanı da yavaşladı.

Bu yazı o haftanın çıkardığı dört cümlelik ihtiyaç listesinin, dört tasarım
kararına nasıl dönüştüğünün hikâyesi. Kısa cevap şu: sorunun çözümü erişimi
genişletmek değil, **erişimi bir kapıdan geçirmek.** Geliştirici üretim
parolasını hiç görmeden veriye bakabilir; bunun için sorgunun kendisinin
denetlenmesi, bir onaydan geçmesi, kalıcı bir ize düşmesi ve sonucun
maskelenmesi yeterli. Bu dördünü kuran şey [QueryProxy](/portfolyo/queryproxy/)
oldu.

## Yanlış çözümler neden yanlıştı

İki hazır cevap vardı ve ikisi de bir hafta içinde elendi.

**"Parolayı ekiple paylaşalım."** Bunun bedeli ilk gün görünmüyor. Parola bir
kez dağıldığında geri toplanamıyor: kimde olduğu bilinmiyor, biri ayrıldığında
döndürmek gerekiyor, ve en önemlisi kimin ne çalıştırdığı sorusunun cevabı
kalmıyor. Bir `UPDATE` yanlış satırı güncellediğinde elinizde yalnızca bir
tahmin oluyor.

**"Herkese salt-okunur kullanıcı açalım."** Bu daha iyi ama yetmiyor. Salt
okunur bir kullanıcı da tablonun tamamını çekebiliyor; kişisel verinin
maskelenmesi diye bir şey yok, ve gerçek teşhisin bir kısmı okumayla bitmiyor —
bazen bozuk bir kaydı düzeltmek gerekiyor. O an geldiğinde yine tek kişiye
dönüyorsunuz.

Ortak kusur şu: ikisi de soruyu **kim** ekseninde çözmeye çalışıyor. Oysa asıl
soru **ne** — hangi ifadenin çalışacağı.

## Birinci ihtiyaç: parolayı görmeden erişmek

İlk karar, kimlik bilgisinin nerede duracağıydı. Bağlantı künyesi uygulamanın
içinde şifreli duruyor, parola hiçbir arayüzde geri gösterilmiyor; sorguyu çalıştıran
da geliştiricinin makinesi değil, portalın kendisi. Geliştiricinin elinde kalan
tek şey yazdığı SQL.

Bunun bedeli gerçek ve peşinen kabul edildi: sorgu artık anında çalışmıyor.
Karşılığında üretim parolası hiçbir zaman bir insanın eline geçmiyor. Bir
kurum için asıl kazanç da bu — erişimi geri almak, artık bir kullanıcıyı
silmekten ibaret.

## İkinci ihtiyaç: her SQL bir kapıdan geçsin

İkinci karar en çok düşündüğümüz yerdi: kapı neye bakacak?

İlk akla gelen yol desen eşleştirmesi — `DELETE` görünce uyar, `DROP` görünce
reddet. Bu yol ilk gün aşılır. Araya yorum sıkıştırmak (`DROP/**/DATABASE`),
ifadeyi bölmek ya da bir alt sorguya saklanmak yeterli oluyor. Düzenli ifadeler
metne bakar; SQL ise metin değil, **yapı.**

O yüzden gelen sorgu ayrıştırılıyor ve ne olduğu çözümlenmiş yapısından
okunuyor. Pratik karşılıkları şunlar:

- `WHERE`'i olmayan bir `UPDATE` ya da `DELETE` reddediliyor. Tabloyu bilerek
  güncellemek isteyen `WHERE 1 = 1` yazıyor — niyetini beyan etmiş oluyor.
- Limitsiz bir `SELECT` varsayılan bir satır sınırıyla dönüyor, üst sınırı aşan
  istek tavana kırpılıyor.
- Veritabanı silme, kullanıcı/rol yönetimi ve sunucu tarafı dosya işlemleri
  koşulsuz engelleniyor — rolü ne olursa olsun.
- Yorumlar ve dize sabitleri normalize edildiği için araya yorum sıkıştırarak
  kapı aşılamıyor.

Bir ayrıntı bu tasarımın özetini veriyor: kapının **anlayamadığı** bir limit
ifadesi kırpılmıyor, reddediliyor. Anlaşılmayan bir sınır, zorlanamayan bir
sınırdır; belirsizlikte doğru davranış geçirmek değil durdurmaktır.

Kapı yalnız yarısı. Diğer yarısı onay: istek bir DBA'ya düşüyor, onaylanınca
kuyrukta çalışıyor. Buradaki sert kural, bir DBA'nın kendi isteğini
onaylayamaması; sistem admini bunu aşabiliyor ve aşma denetim kaydına aşım
olarak düşüyor. Onay üç kanaldan gelebiliyor — web, Slack, Teams — ama üçü de
aynı servise iniyor, çünkü kuralı kanal başına yazmak, kuralın kanal başına
ayrışması demek.

## Üçüncü ihtiyaç: sonradan kimin ne çalıştırdığını bulmak

Üçüncü ihtiyaç bir tarih sorusuydu: üç ay sonra "bu kaydı kim değiştirdi"
sorulduğunda cevap bulunabilmeli.

Buradaki karar, denetim kaydını **değiştirilemez** kılmak oldu; ve bunun
uygulamadaki karşılığı basit üç şey: kayıt tablosunda güncelleme sütunu yok,
güncelleme ve silme model düzeyinde istisna atıyor ve kayıt satırları hiçbir
yabancı anahtar kısıtına bağlanmıyor. Son madde en kolay kaçırılan yer:
bir bağlantı silindiğinde onun geçmişinin de silinmesi, denetim izini tam
ihtiyaç duyduğunuz anda boşaltır.

Dürüst bir sınır: bu, uygulama düzeyinde bir değiştirilemezlik. Veritabanına
doğrudan erişen biri satırı hâlâ değiştirebilir. Kriptografik bir zincir değil,
uygulamanın sızdırmadığı bir kapı — ve neyi vaat ettiğini bilerek kullanmak
gerekiyor.

## Dördüncü ihtiyaç: kritik bilgi maskelensin

Son karar, maskelemenin **nerede** durduğuyla ilgiliydi ve en öğretici olanı bu.

Kolay yol, sonucu kaydedip gösterirken maskelemek. O tasarımda maskesiz veri
diske bir kez yazılıyor ve ondan sonrası temenniye kalıyor: yedek alındığında,
dosya indirildiğinde ya da log'a düştüğünde maskesiz hâl orada duruyor.

Bunun yerine sonuç akışı satır satır okunuyor ve her satır **dosyaya yazılmadan
önce** maskeleniyor. Sonuç deposuna maskesiz bir hücre hiç girmiyor. Fark
kulağa küçük geliyor ama güvenlik açısından kategorik: birinde "maskelemeyi
atlatan bir yol var mı" diye düşünmeniz gerekiyor, diğerinde atlatılacak bir
şey yok.

Aynı disiplin kuralların kendisinde de var. Maskeleme kuralları düzenli ifade
kabul ediyorsa, kötü yazılmış bir ifade işçiyi kilitleyebilir; o yüzden kural
kaydedilirken uzun bir prob dizesiyle sınanıyor. Ve çalışma anında bir ifade
değerlendirilemezse değer açıkta bırakılmıyor, maskeleniyor — belirsizlikte
kapalı tarafa düşmek, kapı kuralının aynısı.

## Bunun toplam bedeli

Dürüst olmak gerekirse bu kurulumun bir maliyeti var ve saklanacak bir tarafı
yok.

Sorgu artık anlık değil; birinin onaylaması gerekiyor ve bu, gece yarısı bir
olayda sürtünme demek. Ekip yeni bir servis işletiyor. Ve en önemlisi, onay
kuyruğu bir ritüele dönüşürse — yani DBA bakmadan onaylıyorsa — bütün yapı
tiyatroya dönüşüyor; o hâlde parolayı paylaşmaktan tek farkı, yanlış bir
güven duygusu vermesi olur.

Karşılığında alınan şey ise şu: teşhis artık bir kişinin takvimine bağlı değil,
üretim parolası kimsede yok, ve üç ay sonra sorulacak soruların cevabı duruyor.
Bir hafta boyunca teşhis edemediğimiz bug'ların bana öğrettiği şey, erişim
yönetiminin bir izin meselesi değil bir **tasarım** meselesi olduğuydu.

Aracın kendisi açık kaynak ve self-hosted; kurduğum yol ile kararların
gerekçesi [Labs kaydında](/labs/queryproxy-lab/), ürünün künyesi
[portfolyoda](/portfolyo/queryproxy/).

---

### Kaynaklar

- [QueryProxy: SQL guards](https://queryproxy.com/docs/sql-guards/)
- [QueryProxy: Data masking](https://queryproxy.com/docs/data-masking/)
- [QueryProxy: Approval workflow](https://queryproxy.com/docs/approval-workflow/)
