Posta Loglarını Aranabilir Yapmak: Postfix Loglarını ELK ve Loki ile Soruşturmak
Postfix tek bir maili beş ayrı satıra, üç farklı process'e ve dakikalar arasına dağıtır. queue_id etrafında yapılandırılmış, indekslenmiş loglar olmadan "bu mail teslim edildi mi" sorusunu grep ile cevaplamak imkansızlaşır. Loki ve ELK ile gerçek parser'lar, teslimat ve güvenlik soruşturması sorguları.
EvilMail Team31 Temmuz 202612 dk okuma
Bir müşteri "gönderdiğim mail karşı tarafa ulaşmadı" diye yazdığında, elinizde sadece /var/log/mail.log varsa saat işlemeye başlar. O tek mailin izi log dosyasında beş ayrı satıra, üç farklı process'e (smtpd, cleanup, qmgr, smtp) ve çoğu zaman birbirinden saniyeler hatta dakikalar uzaklıktaki zaman damgalarına dağılmıştır. Hepsini birbirine bağlayan tek şey 10-11 karakterlik bir queue_id. grep ile bu ipin ucunu bulmak mümkün, ama bir bounce fırtınası, spoof denemesi ya da ele geçirilmiş bir hesabın outbound spam'i sırasında binlerce satır arasında korelasyon kurmak elle yapılabilir bir iş olmaktan çıkar.
Çözüm log satırlarını düz metin olmaktan çıkarıp queue_id etrafında yapılandırılmış, indekslenmiş olaylara dönüştürmek. Bu yazıda Postfix/Dovecot/OpenDMARC loglarını Loki veya ELK'ya aktarmanın gerçek konfigürasyonlarını, hangi durumda hangisini seçeceğinizi ve bir maili uçtan uca izlemekten credential stuffing tespitine kadar somut soruşturma sorgularını göstereceğim.
Posta Loglarını ELK ve Loki ile Aranabilir Yapmak | evilmail.pro — EvilMail Blog
Postfix logu neden grep'lenemez
Tek bir başarılı teslimatın hayat döngüsü log dosyasında şöyle görünür:
Bu beş satırı tek bir olay yapan alan 4Qk8bT1abc queue_id'si. Dikkat edin: connect satırında henüz queue_id yok, message-id başka bir process'ten geliyor, teslimat sonucu (status=sent) ise en sonda. Bir mailin akıbetini öğrenmek için önce queue_id'yi bulup sonra o queue_id ile tekrar grep atmanız gerekir. deferred bir mailde aynı queue_id günlerce, her retry denemesinde yeni satırlar üretir.
journalctl | grep üç yerde çöker: multi-line korelasyon yok (queue_id'yi elle taşımanız gerekir), retention yok (log rotate olunca geçmiş uçar) ve structured filtreleme yok (status=deferred AND domain=gmail.com gibi bir sorgu yazamazsınız). Hedef her satırı queue_id, status, to, dsn, delay alanlarına parse edip aranabilir hale getirmek.
Loki mı ELK mi: dürüst karar
İkisi de aynı problemi çözer ama maliyet profilleri taban tabana zıttır.
Loki, sadece etiketleri (label) indeksler; log gövdesini sıkıştırılmış chunk'larda tutar. Tek binary olarak ~256-512MB RAM ile çalışır, LogQL ile sorgulanır. Küçük/orta bir mail sunucusunda (tek ya da birkaç domain, günde yarım milyon satırın altında) en ucuz ve en az bakım isteyen seçenek budur. Tek kritik disiplin: label kardinalitesini düşük tutmak. queue_id'yi label yaparsanız Loki'nin indeksi patlar ve performans çöker.
ELK / OpenSearch, tam metin ters indeks (inverted index) kurar. Kibana ile ağır agregasyon, korelasyon ve dashboard yapabilirsiniz; her alanda serbest arama mümkün. Karşılığında 4GB+ RAM, JVM heap tuning ve operasyonel bakım ister. JVM heap'i genellikle toplam RAM'in yarısı ama asla 32GB üstü ayarlanmaz (compressed oops sınırı).
Pratik eşik:
Loki seçin eğer: günde 500 bin satırın altı, tek/az sayıda domain, bütçe ve RAM kısıtlı, temel teslimat + auth failure takibi yeterli.
ELK seçin eğer: multi-domain, uzun retention, güvenlik korelasyonu (birden çok log kaynağını çapraz sorgulama), zengin dashboard ve serbest metin araması gerekiyor.
Her iki durumda da ortak katman bir collector agent: Promtail (Loki için), Filebeat/Logstash (ELK için) ya da her ikisine de yazabilen Vector.
Pipeline: log → parse → indeks
Önce Postfix'i syslog karmaşasından ayırın. rsyslog ile mail facility'sini kendi dosyasına yönlendirin ki agent temiz bir kaynağı tail etsin:
Agent bu dosyayı tail eder, her satırı regex/grok/VRL ile alanlara ayırır ve queue_id'yi ayrı bir field yapar. Vector'ü tercih etmemin nedeni tek agent'ın hem parse etmesi hem de aynı anda hem Loki'ye hem Elasticsearch'e fan-out yapabilmesi; Promtail + Logstash ikilisine göre çok daha az hareketli parça. journald'dan doğrudan okumak isterseniz Promtail'in journal scrape'i ve Vector'ün journald source'u da mevcut, ama dedicated dosya daha öngörülebilir parse verir.
Loki tarafı: Promtail pipeline_stages ile parse
Promtail'de parse işi pipeline_stages altında yapılır. Kritik nokta: yüksek kardinaliteli alanları (to, message-id, queue_id) label yapmayın; onları log satırında bırakın, sadece düşük kardinaliteli host, program, status label olsun. İki ayrı regex stage kullanıyorum: birincisi her satırdan program'ı çıkarır, ikincisi yalnızca teslimat sonucu taşıyan satırlardan queue_id ve status'u yakalar.
Zaman damgası için ayrı bir timestamp stage koymadım: canlı logu tail ettiğimiz için Promtail'in ingest zamanı yeterince doğrudur, üstelik geleneksel syslog damgası (Jul 04 09:12:01) yıl içermez ve RFC3339 ile eşleşmez; yanlış bir format tanımı sessizce satırları yanlış zamana yerleştirir. status düşük kardinalitelidir (sent, deferred, bounced, expired — dört değer), o yüzden label yapmak güvenli. queue_id ise named capture olarak parse edilir ama label'a alınmaz; LogQL ile filtrelenebilir kalır. Teslimat izleme ve bounce oranı sorguları:
logql
# Tek bir maili queue_id ile uçtan uca izle
{job="postfix"} |= "4Qk8bT1abc"
# Son 5 dakikadaki bounce oranı
sum(rate({job="postfix"} | logfmt | status="bounced" [5m]))
# Gmail'e giden deferred maillerin sayımı
count_over_time({job="postfix"} |= "gmail-smtp-in" |~ "status=deferred" [1h])
ELK tarafı: Filebeat/Vector + grok
ELK tarafında iki yol var. Filebeat'in hazır postfix modülü çoğu satırı ECS alanlarına (postfix.queue_id, email.delivery.status) otomatik eşler; hızlı başlangıç için idealdir. Daha fazla kontrol isterseniz Vector'ün VRL'i ile parse edip hem Loki hem Elasticsearch sink'ine aynı anda yazın:
Elasticsearch tarafında iki şeyi baştan ayarlayın: index template (alan tiplerini sabitler, queue_id keyword olmalı ki agregasyon yapabilesiniz) ve ILM policy. Mail logları hızla büyür; ILM ile hot fazı 7 gün, ardından delete 90 gün gibi bir yaşam döngüsü tanımlayın ki disk şişmesin. Kibana'da bir Data View oluşturup @timestamp alanını zaman alanı seçmeniz yeterli.
Teslimat soruşturması: "bu mail gitti mi?"
Müşteri queue_id verdiyse iş kolay; vermediyse alıcı adresinden veya gönderenden başlayın. Kibana KQL ile:
text
postfix.queue_id:"4Qk8bT1abc"
email.delivery.status:"deferred" and destination.domain:"gmail.com"
`status=deferred` + `421`/`451`: geçici red. Gmail'de 421-4.7.0 ... temporary rate limit görürseniz bu greylisting ya da rate-limit demektir; mail kuyrukta bekler, Postfix retry eder. Panik yok, ama sürekli tekrarlıyorsa reputation sorununuz var.
`status=bounced` + `5.x.x`: kalıcı red. Alıcı yok, domain reddetti ya da içerik spam'e takıldı.
En çok bounce alan alıcı domainleri: Kibana'da destination.domain üzerinde bir terms agregasyonu tek bir domainde yığılmayı (ör. tüm bounce'lar outlook.com) anında gösterir — bu genelde bir bloklanma sinyalidir.
Güvenlik soruşturması: spoof, brute-force, outbound abuse
Aynı pipeline güvenlik olaylarında çok daha değerli. Üç sinyali izleyin:
Credential stuffing / brute-force. Dovecot başarısız login'leri şöyle loglar:
Kaynak IP başına dakikadaki fail sayısını sayın. Grafana'da rip üzerinde bir eşik (ör. dakikada 10 fail) alert kuralı, dağıtık bir login denemesini o IP'yi fail2ban'a düşürmeden önce yakalar.
Spoof / DMARC fail. OpenDMARC sonucu logda net durur:
DMARC fail satırlarını gönderen domaine göre gruplayınca kimin adına spoof denendiğini görürsünüz. Kendi domaininiz spoof ediliyorsa bu, DMARC politikanızı p=quarantine'den p=reject'e çekmenin işaretidir.
Ele geçirilmiş hesap / outbound abuse. En sinsi olan bu. Meşru bir sasl_username aniden yüksek hacimde status=sent üretmeye başlar. Tek hesap için saatlik outbound sayımına bir Grafana alert koyun (ör. >200/saat). Ek bir kontrol: sasl_username ile mesajın from adresi uyuşmuyorsa (authenticated user ≠ envelope sender) bu neredeyse her zaman kötüye kullanım demektir.
Alan haritası ve retention
Parse'tan sonra elinizde kalması gereken kanonik alanlar ve nereye yazıldıkları:
Kritik uyarı: mail loglarında e-posta adresleri PII'dır. GDPR açısından retention'ı kısa tutun (30-90 gün pratik bir aralık), erişimi kısıtlayın ve mümkünse Grafana/Kibana'yı SSO arkasına koyun. ILM'de delete fazını bu retention'a göre ayarlayın; "her ihtimale karşı yıllarca sakla" hem yasal risk hem gereksiz disk maliyetidir.
Kurulum checklist
Postfix'i rsyslog ile dedicated dosyaya (/var/log/mail.log) yönlendirin.
Agent kurun (Loki için Promtail, ELK için Filebeat, ikisine birden için Vector).
Parse'ı doğrulayın: birkaç örnek satırın queue_id/status alanlarına gerçekten ayrıldığını gözle görün.
Loki'de kardinaliteyi denetleyin: sadece host/program/status label olsun, queue_id asla.
ELK'da index template + ILM policy (hot 7g, delete 90g) tanımlayın.
En az iki dashboard kurun: teslimat oranı (sent/deferred/bounced) ve auth failure (IP başına fail).
Alert kuralları: bounce oranı ani artış, IP başına brute-force eşiği, hesap başına outbound hacim.
Log rotasyonu ve disk kullanımını izleyin; agent'ın rotate sonrası dosyayı doğru takip ettiğini kontrol edin.
Bu pipeline kurulduktan sonra "bu mail gitti mi" sorusu dakikalardan saniyelere iner: queue_id'yi yapıştırırsınız, mailin tüm yaşam döngüsü tek ekranda önünüzdedir. Aynı altyapı, bir bounce fırtınası ya da ele geçirilmiş hesap anında sizi grep maratonundan kurtarıp doğrudan kök nedene götürür.