KVKK Uyumlu E-posta Log Saklama ve Otomatik İmha Mimarisi
Bir denetimde "e-posta loglarınızı ne kadar tutuyorsunuz?" sorusuna verilecek tek doğru cevap somut bir sayıdır. Postfix logu, IMAP içeriği, uygulama audit'i ve şifreli yedekleri katman katman ayırıp her birine ayrı saklama süresi ve geri alınamaz otomatik imha uygulayan, denetlenebilir bir teknik mimari.
EvilMail Team4 Ağustos 202613 dk okuma
Bir KVKK denetiminde en sık düşülen soru teknik değil, envanter sorusudur: "E-posta loglarınızı ne kadar süre tutuyorsunuz ve süre dolunca ne oluyor?" Buna "bilmiyoruz" ya da "hepsi sunucuda duruyor" cevabı vermek, tek başına md.4 (amaçla sınırlı, gerektiği süre kadar muhafaza) ve md.7 (silme, yok etme, anonim hale getirme) ihlalidir. Problem log tutmanız değil. Log tutmak meşru menfaat ve kanuni yükümlülükle rahatça savunulur. Savunulamayan şey, o logun sınırsız ömrü ve sildiğinize dair hiçbir kanıtınızın olmayışıdır.
İşin mühendislik tarafı da tam burada çirkinleşiyor. "Her şeyi 90 gün sonra sil" diyen tek bir cron yazmak hem uyumsuz hem tehlikelidir; çünkü Postfix transport logu, IMAP posta kutusu içeriği, uygulama audit kayıtları ve şifreli yedekler birbirinden tamamen farklı hukuki gerekçelere ve sürelere tabidir. Fatura verisini 90 günde silen o cron, sizi VUK ihlaline sokar. Aşağıda kişisel veriyi katman katman ayrıştıran; her katmana ayrı saklama süresi ve geri alınamaz, denetlenebilir bir imha mekanizması bağlayan somut mimari var.
KVKK loglardan tam olarak ne istiyor
Beş dayanağı bilmek yeterli:
md.4 – İlkeler. Kişisel veri, işlendikleri amaçla bağlantılı, sınırlı ve ölçülü; ilgili mevzuatta öngörülen veya işlendikleri amacın gerektirdiği süre kadar
KVKK E-posta Log Saklama Süreleri ve Otomatik İmha Rehberi — EvilMail Blog
muhafaza edilir. "Lazım olur" diye tutmak bu maddeyi tek başına deler.
md.5 – Hukuki sebepler. Transport logunu "sistem güvenliği ve meşru menfaat" ile, faturayı "kanuni yükümlülük" ile tutarsınız. Her kaynağın gerekçesi ayrı; dolayısıyla süresi de ayrı.
md.7 + İmha Yönetmeliği. "Kişisel Verilerin Silinmesi, Yok Edilmesi veya Anonim Hale Getirilmesi Hakkında Yönetmelik" (RG 28.10.2017): Saklama ve İmha Politikası olan veri sorumlusu periyodik imha süresini bu politikada belirtir ve bu süre her hâlde 6 ayı geçemez. VERBİS'e kayıt yükümlüsüyseniz bu politika zorunludur.
Log'ta kişisel veri sayılan alanlar somut: kaynak/hedef IP, e-posta adresi, envelope MAIL FROM/RCPT TO, message-id ile bir kimliğe bağlanabilen kayıtlar, User-Agent ve X-Originating-IP. Yani standart bir mail.log satırı baştan sona kişisel veridir.
Bir de klasik tuzak: 5651 sayılı kanun. Erişim sağlayıcılara trafik loglarını ~1-2 yıl tutma yükümlülüğü getirir. Bağımsız bir mail/SaaS operatörü tipik olarak "yer sağlayıcı" konumundadır ve bu uzun süre transport loglarınızın çoğu için gerekçe değildir. Fazla saklamayı 5651 ile haklı çıkarmaya çalışmak, denetimde ters teper.
Katmanları ayır: her verinin ayrı bir saati var
Önce envanter. E-posta altyapısındaki kişisel veri kaynaklarını, gerçek dosya yolları ve tablolarıyla listeleyin:
Temp-email içeriği — /var/mail/vhosts/{domain}/{username}/ (5000:5000). evilmail.pro'nun doğası gereği bu veri saatler-günler ömürlüdür; privacy-by-design'ın ders kitabı örneği.
Transport metadata — Postfix/Dovecot mail.log: IP, envelope, teslimat durumu.
Uygulama DB'si — login_history (IP + User-Agent), support_tickets/ticket_messages, app_promo_events.
Redis — oturum ve cache; genelde zaten TTL'li ama denetlenmemiş.
Nginx access log — istek IP'leri, referer.
Şifreli yedekler — en zor katman, çünkü yedekten tek bir satır "silmek" pratikte imkânsızdır.
Aşağıdaki harita bu yazının kalbi. "Hepsi aynı süre değil" mesajını tek bakışta veriyor.
Süreleri belirlemek: gerekçesiz sayı yazma
Her sürenin arkasında bir amaç olmalı. Politikaya "90 gün" yazıp geçmek denetimde sizi kurtarmaz; "neden 90 gün" sorusunun cevabı gerekir.
Transport log (troubleshooting, spam/abuse analizi): 30-90 gün. Bir teslimat şikâyetini geriye dönük incelemek için 30 gün pratikte yeterli; 90 gün üst sınır.
Güvenlik/audit log (yetkisiz erişim tespiti): 180-365 gün. Bir hesap ele geçirmesi çoğu zaman aylar sonra fark edilir; bu katmanda daha uzun tutmanın somut güvenlik gerekçesi vardır.
`login_history`: 12 ay. Sonra kaydı silmek yerine anonimleştirin — IP'yi /24'e indirin, User-Agent'ı sabitleyin. Kayıt istatistik olarak kalır, kişisel veri olmaktan çıkar.
Temp-email içeriği: saatler-günler. Baştan kısa ömürlü tasarlanır; imha zaten ürünün varsayılan davranışıdır.
Fatura/işlem verisi:VUK md.253 gereği 5 yıl (TTK md.82 uyarınca ticari defterler için 10 yıl). Bu veri imha döngüsünden ayrılır ve süresi dolmadan silinmez. Silme cron'unuz bu tablolara asla dokunmamalı.
Kritik ayrım: anonimleştirme ≠ silme. Anonimleştirme ancak geri döndürülemezse geçerlidir. IPv4'te yalnızca son okteti maskelemek zayıftır; /24'e indirmek ve ilişkilendirilebilir diğer alanları da temizlemek gerekir. Test şu: elinizdeki tüm kayıtlarla o satırı tekrar bir gerçek kişiye bağlayabiliyor musunuz? Bağlayabiliyorsanız anonimleştirmediniz, sadece maskelediniz.
Uygulama 1 — dosya sistemi logları: logrotate + shred
Yaygın yanılgı, logrotate'in compress ile eski logu gzip'leyip bir süre sonra üzerine yazmasını "imha" sanmaktır. gzip'li rotate silme değildir; sadece sıkıştırılmış kişisel veriyi diskte tutar. İmha, postrotate'te ya da ayrı bir purge adımında güvenli silme ile yapılır.
Dürüst uyarı: SSD, btrfs/zfs gibi CoW dosya sistemleri ve journald'ın kendi yapısı nedeniyle shred'in fiziksel üzerine-yazma garantisi yoktur — wear-leveling ve kopyala-yaz, eski blokları başka yerde bırakabilir. Bu yüzden diskte at-rest encryption (LUKS) + aşağıdaki crypto-shredding kombinasyonu, tek başına shred'e güvenmekten daha sağlamdır.
Uygulama 2 — veritabanı, mailbox ve yedekler
DB tarafında imha iki biçimde olur: eski kayıtları anonimleştirmek veya tamamen silmek. login_history için anonimleştirme yeterli, app_promo_events için tam DELETE mantıklı:
sql
-- 12 ayı geçen giriş kayıtlarında IP'yi /24'e indir, UA'yı sabitle
UPDATE login_history
SET ip_address = regexp_replace(ip_address, '\.\d+$', '.0'),
user_agent = 'anonymized'
WHERE created_at < now() - interval '12 months'
AND user_agent <> 'anonymized';
-- promo analitiği 90 günde tamamen imha, satır sayısını tutanağa yaz
WITH del AS (
DELETE FROM app_promo_events
WHERE created_at < now() - interval '90 days'
RETURNING 1
)
INSERT INTO imha_log (layer, method, affected_rows, run_by)
SELECT 'app_promo_events', 'delete', count(*), 'systemd:evilmail-purge'
FROM del;
Bunu cron değil, systemd timer ile çalıştırın. Timer, tetiklemeyi journald'a otomatik loglar, Persistent=true ile sunucu kapalıyken kaçırılan çalıştırmayı telafi eder ve systemctl list-timers ile denetlenebilir. Denetimde "silme gerçekten çalıştı mı" sorusuna kanıt üretmesi cron'dan çok daha kolaydır.
Yedekler — asıl zor problem. Şifreli bir yedekten tek bir kullanıcının verisini "silmek" imkânsıza yakındır; yedek zaten değişmezlik için vardır. Çözüm crypto-shredding: her yedek dönemini ayrı bir anahtarla (örneğin aylık AES-256) şifreleyin. Saklama süresi dolunca dosyayı silmeye çalışmak yerine o dönemin anahtarını imha edin. Veri fiziksel olarak diskte kalsa bile matematiksel olarak erişilemez hâle gelir; bu, Yönetmelik'in "yok etme" tanımı için geçerli bir yöntemdir.
bash
# Aylık anahtar üret, yedeği o anahtarla şifrele
KEY_ID=$(date +%Y%m)
openssl rand -base64 32 > /etc/evilmail/keys/backup-$KEY_ID.key
tar czf - /var/backups/evilmail | \
openssl enc -aes-256-cbc -pbkdf2 \
-pass file:/etc/evilmail/keys/backup-$KEY_ID.key \
-out /var/backups/enc/evilmail-$KEY_ID.tar.gz.enc
# 5 dönem (5 ay) sonra: yedeği değil, ANAHTARI imha et -> crypto-shred
find /etc/evilmail/keys -name 'backup-*.key' -mtime +150 \
-exec shred -u -n 3 {} \;
İmha sürecinin en çok atlanan parçası tutanaktır. Her purge çalıştırması; tarih-saat, katman/kaynak, uygulanan yöntem (silme / anonimleştirme / crypto-shred), etkilenen kayıt sayısı ve çalıştıran süreç bilgisiyle ayrı bir imha_log tablosuna yazılmalı. Bu tablo kişisel veri içermez, dolayısıyla uzun süre saklanır ve periyodik imha raporunuzun ham kaynağıdır.
Periyodik imha takviminizi Yönetmelik'e göre ayarlayın: Saklama ve İmha Politikanızda beyan ettiğiniz imha periyodu her hâlde 6 ayı geçemez. Timer günlük çalışsa bile, dönem sonlarında imha_log'tan otomatik bir özet raporu üretmek ve bunu VERBİS beyanınızdaki sürelerle karşılaştırmak, tutarsızlığı erken yakalar. Beyanda "12 ay" yazıp sistemde 400 gün tutan bir login_history, denetimde en pahalı hatadır.
Dağıtımdan önce kontrol listesi
Kişisel veri envanteri çıkarıldı mı — her dosya yolu ve tablo tek tek listelendi mi?
Her kaynağa süre + hukuki sebep atandı mı; "lazım olur" gerekçesi hiçbir yerde kalmadı mı?
VUK 5 yıl (fatura/işlem) ve varsa TTK 10 yıl istisnası imha döngüsünden ayrıldı mı; purge script bu tablolara dokunmuyor mu?
5651'i transport logları için yanlışlıkla gerekçe olarak kullanmadığınızdan emin misiniz?
logrotate'te gzip rotate'in silme olmadığı biliniyor mu; postrotate/lastaction gerçekten shred -u çağırıyor mu?
journald MaxRetentionSec ayarlandı ve --vacuum-time ile teyit edildi mi?
systemd timer'lar enable --now ve Persistent=true mı; list-timers ile son çalıştırma görünüyor mu?
Anonimleştirme geri alınamaz mı — IP /24'e indi, UA sabitlendi, ilişkilendirilebilir alan kalmadı mı?
Yedekler için crypto-shredding kurulu mu; dönem anahtarları süre dolunca gerçekten imha ediliyor mu?
Her çalıştırma imha_log'a tutanak yazıyor mu; bu tablo kişisel veri içermiyor mu?
Sistemdeki gerçek süreler, VERBİS beyanı ve Saklama/İmha Politikası dokümanıyla birebir eşleşiyor mu?
Bu mimaride imha bir PDF politikası değil, çalışan ve kendini loglayan bir sistemdir. evilmail.pro'nun temp-email tarafında veri zaten saatlik ömürle doğar — privacy-by-design en uç noktada uygulanır — ama aynı katmanlı yaklaşım her mail operatörü için geçerli referans modeldir: veriyi tek bir cron'a değil, ait olduğu hukuki rejime göre ayrı saatlere bağlarsınız.