Bir Postfix/OpenDKIM kutusuna SSH ile bağlanıp şu tek komutu çalıştırın:
ls -l /etc/opendkim/keys/evilmail.pro/
# -rw-r--r-- 1 opendkim opendkim 1679 Mar 12 09:41 mail.private
# -rw-r--r-- 1 opendkim opendkim 512 Mar 12 09:41 mail.txt-rw-r--r--, yani 644. Bu, sunucudaki her yerel kullanıcının DKIM özel anahtarını okuyabildiği anlamına gelir. Ele geçirilmiş bir PHP süreci, komşu bir shell hesabı, yanlış çalışan bir cron — hepsi bu dosyayı cat'leyebilir. DKIM özel anahtarı sızdığında olan şudur: anahtar, alan adınızın imza kimliğidir. Kimlik sızarsa saldırgan sizin selektörünüzle imzalı, SPF ve DMARC hizalamasından temiz geçen sahte e-posta gönderir. Alıcının Gmail'i "bu gerçekten evilmail.pro" der. p=reject olan DMARC politikanız bile devreye girmez, çünkü imza teknik olarak geçerlidir.
Tezi baştan koyalım: sırları gizli tutmak bir şifreleme değil, bir erişim sınırı ve işletim disiplini meselesidir. Diski LUKS ile şifrelemek, sistem açıkken çalışan bir süreç anahtarı okuyabildiği sürece işe yaramaz. Bu yazı teoriyle değil icraatla ilgili: mod bitleri, süreçle sırrı buluşturma yolları ve sıfır kesintili rotasyon.
Tehdit modeli: sır tam olarak kimden saklanıyor?
Bir sırrın üç farklı hâli vardır ve her biri farklı bir sınırla korunur:
- Diskte durağan (at-rest):
mail.private,.env,sasl_passwd. Burada tek savunma dosya izinleri ve sahipliktir. - Süreç belleğinde:
opendkim,node,postfixsüreçleri sırrı RAM'de tutar. Buraya/proc/<pid>/environveya bir core dump ile ulaşılır. - Türev kopyalar: Yedekler, loglar, Git geçmişi,
docker inspectçıktısı, APM/Sentry'nin dumpladığı ortam değişkenleri. Sırların çoğu buradan sızar, çünkü kimse bu kopyaları "sır" olarak düşünmez.
Saldırgan yolları soyut değil: paylaşımlı hosting'te izni gevşek bir anahtar dosyasını okuyan komşu kullanıcı; internete açık bir .git/ klasörü (wget -r https://site/.git/); hata anında tüm process.env'i olduğu gibi Sentry'ye gönderen bir exception handler; docker inspect çalıştırabilen herkesin gördüğü -e SMTP_PASSWORD=... ortam değişkeni. Hiçbirinde şifre kırılmıyor — sadece bir okuma sınırı eksik.
Dosya izinleri: DKIM özel anahtarını doğru kilitlemek
En yoğun bölüm burası, çünkü sunucuların çoğunda hata tam olarak burada. opendkim-genkey ürettiği anahtarı çoğu dağıtımda 600 yapar, ama insanlar dosyayı scp, rsync, tar ya da bir deploy scriptiyle taşırken izin ve sahipliği bozar. Doğru hedef nettir:
# Anahtar üretimi (2048-bit; 1024 artık zayıf kabul edilir)
opendkim-genkey -b 2048 -s mail -d evilmail.pro
# -> mail.private (özel anahtar) + mail.txt (DNS kaydı)
chown opendkim:opendkim /etc/opendkim/keys/evilmail.pro/mail.private
chmod 600 /etc/opendkim/keys/evilmail.pro/mail.private
chmod 700 /etc/opendkim/keys/evilmail.pro # dizinin kendisi640 ile 600 farkı önemli: 640, dosyanın grup üyelerine okuma verir. opendkim grubuna başka servisler eklenmişse (olur), grup okuması bir sızıntı yoludur. OpenDKIM anahtarı kendi kullanıcısı olarak okur; başka hiçbir kimliğin — özellikle www-data/php-fpm kullanıcısının — bu dosyaya erişmesi için meşru bir sebep yoktur. Dizini 700 yapmak da şart: 755 bir dizinde dosyalar 600 olsa bile içerik listelenebilir ve isimler tahmin edilebilir kalır.
Sinsi kısım umask. Deploy scriptleriniz umask 022 altında çalışıyorsa, oluşturdukları her yeni dosya 644 doğar. Sır üreten hiçbir otomasyonun 022 altında çalışmasına izin vermeyin:
umask 077 # yeni dosyalar 600, yeni dizinler 700
# ...sır üreten işlemler...Tek bir kullanıcıya cerrahi erişim gerekiyorsa (örneğin bir imzalama proxy'si), grup izinlerini gevşetmek yerine ACL kullanın:
setfacl -m u:opendkim:r /etc/opendkim/keys/evilmail.pro/mail.private
getfacl /etc/opendkim/keys/evilmail.pro/mail.private # doğrulaBütün kutuyu tek komutla denetleyin — 600 olmayan her anahtar dosyasını yakalar:
find /etc/opendkim/keys -type f ! -perm 600 -lsSızan anahtarın somut sonucunu görmek için DNS'teki eşleşen public kayda bakın:
mail._domainkey.evilmail.pro. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."Bu p= içindeki public anahtar, elinizdeki private ile matematiksel bir çift. Saldırgan private'ı ele geçirince, bu selektörle imzaladığı her mesaj bu public kayda karşı doğrulanır — yani _dmarc.evilmail.pro'daki p=reject politikanız onu durduramaz, çünkü imza geçerlidir. DKIM sızıntısı bu yüzden sadece bir güvenlik olayı değil, aynı zamanda bir itibar (reputation) felaketidir: alan adınız artık spam gönderiyor gibi görünür ve blocklist'lere düşer.
SMTP parolaları ve relay kimlikleri
Postfix'i bir smarthost/relay üzerinden gönderim yapacak şekilde kurduysanız kimlik bilgileri sasl_passwd dosyasındadır ve buradaki klasik hata, postmap sonrası iki dosyayı da kilitlemeyi unutmaktır:
cat /etc/postfix/sasl_passwd
# [smtp.relay.example]:587 submission-user:REAL_PASSWORD_HERE
postmap /etc/postfix/sasl_passwd # sasl_passwd.db üretir
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db.db dosyası da düz metin parolayı okunabilir biçimde tutar; sadece kaynağı 600 yapıp derlenmiş hash-map'i 644 bırakmak sırrı açıkta bırakır. Aynı disiplin Dovecot passdb tarafında da geçerli.
Kritik operasyonel karar şu: uygulamanıza veya relay'e asıl mailbox parolasını gömmeyin. Her gönderim yolu için ayrı, tek amaçlı bir submission kimliği açın. Böylece o kimlik sızarsa yalnızca onu iptal edersiniz; ana hesap ve ona bağlı IMAP erişimi etkilenmez. Uygulama tarafında SMTP parolasını .env'de düz tutmak ise bir sonraki bölümün tam konusu.
Uygulama sırları: .env, ortam değişkenleri ve secret manager
.env dosyası pratik ama kaç yerden sızdığını çoğu takım hafife alır.
Git geçmişi. .gitignore'a .env eklemek geleceği korur, geçmişi değil. Dosya bir kez commit'lendiyse repoyu klonlayan herkes onu git log -p ile görür. Silmek geçmişi yeniden yazmayı gerektirir:
git filter-repo --path .env --invert-paths # BFG de kullanılabilir
# sonra force-push ve TÜM sızan sırları rotate etSilmiş olmanız sırrın sızmadığı anlamına gelmez — commit'lendiği andan itibaren sızmış kabul edin ve rotate edin. Düzenli tarama için CI'a bir tarayıcı koyun:
gitleaks detect --source . # veya
trufflehog filesystem .Client'a sızma (Next.js somut örneği). evilmail bir Next.js uygulaması; buradaki tuzak NEXT_PUBLIC_ prefiksidir. Bu prefiksli her değişken build sırasında client bundle'ına gömülür ve tarayıcıdaki herkes görür. Bir API sırrını NEXT_PUBLIC_SMTP_KEY olarak adlandırmak, onu tam anlamıyla yayınlamaktır. Sırlar prefikssiz kalmalı ve yalnızca server tarafında (route handler, server action) okunmalı.
Süreç ortamı okunabilir. Ortam değişkeni bir "kilit" değildir. Aynı kullanıcı ya da root şunları yapabilir:
cat /proc/$(pgrep -n node)/environ | tr '\0' '\n'
docker inspect evilmail | jq '.[].Config.Env'Bu yüzden yüksek değerli sırlar için dosya-tabanlı sır ortam değişkeninden daha güvenlidir. Docker'da secrets kullanın; sır tmpfs üzerinde /run/secrets/<name> altında belirir, docker inspect içinde görünmez ve _FILE konvansiyonuyla okunur:
services:
app:
secrets: [smtp_password]
environment:
SMTP_PASSWORD_FILE: /run/secrets/smtp_password
secrets:
smtp_password:
file: ./secrets/smtp_password.txt # 600, repoda değilsystemd altında çalışıyorsanız LoadCredential= sırrı sürece izole biçimde verir; EnvironmentFile= kullanacaksanız o dosya 600 ve root:root olmalı.
Gerçek secret manager ne zaman gerekir? Yüzlerce hizmeti olan bir organizasyon için Vault veya AWS Secrets Manager mantıklıdır. Ama tek bir mail kutusu için bu ağırdır. Pragmatik orta yol SOPS + age: sırları şifreli, versiyonlanabilir bir dosyada Git'te tutarsınız; yalnızca özel anahtarı olan makine çözer.
sops -e -i secrets.enc.yaml # yerinde şifrele
# çözme anahtarı: ~/.config/sops/age/keys.txt -> chmod 600Şifreli dosyayı repoda tutabilmeniz, geçmiş sızıntısı riskini fiilen ortadan kaldırır — çünkü commit'lenen zaten çözülemez.
Rotasyon ve sızıntı sonrası: anahtarı kesintisiz değiştirmek
Sır rotasyonu bir "olur da lazım olur" işi değil, planlı bir takvim işidir; DKIM için 6 ayda bir makul bir aralıktır. DKIM rotasyonunun püf noktası şudur: "eskiyi sil, yeniyi koy" DEĞİL, örtüşmeli geçiştir. Eski selektörle imzalanmış ve hâlâ posta kutularında bekleyen mesajlar, eski public kayıt yayında kaldığı sürece doğrulanmaya devam eder. Yeni selektörü yayınlar, DNS yayılmasını beklersiniz, imzalamayı yeni anahtara geçirir ve eskiyi ancak en az bir TTL sonra kaldırırsınız.
Somut adımlar:
# 1) Yeni anahtar + selektör üret
opendkim-genkey -b 2048 -s s2048 -d evilmail.pro
# 2) mail.txt içeriğini s2048._domainkey.evilmail.pro TXT olarak yayınla
# 3) DNS yayılmasını bekle (yeni kayıt olduğundan genelde birkaç dakika), doğrula:
dig +short TXT s2048._domainkey.evilmail.pro
# 4) OpenDKIM'de imzalama selektörünü değiştir: Selector s2048 -> reload
# 5) En az 1×TTL (genelde 3600s) sonra eski mail._domainkey kaydını sil2048-bit RSA neden? 1024-bit artık kırılabilir kabul edilir. Bir uyarı: DNS TXT kayıtlarının 255 karakter sınırı vardır, 2048-bit anahtar bu sınırı aşar ve string birleştirme ("...p1..." "...p2...") ile bölünür — çoğu zone editörü bunu otomatik yapar ama elle giriyorsanız dikkat edin. ED25519 daha kısa ve modern, ama 2026 itibarıyla hâlâ ED25519 imzasını doğrulamayan alıcılar var; bu yüzden pratik konfigürasyon RSA + ED25519 çift imzadır — uyumlu alıcı ED25519'u, gerisi RSA'yı doğrular.
Sızıntı şüphesinde ise takvim geçersiz, dakikalar önemlidir: yeni selektörü yayınla, imzalamayı derhal geçir, sızan selektörün DNS kaydını kaldır (bu, çalınan anahtarla üretilecek yeni imzaları anında geçersiz kılar), sonra SMTP/API kimliklerini rotate et. Rotasyon sırasında bağımlı servisleri kırmamak için önce yeni kimliği ekleyip her iki kimliğin de çalıştığı bir örtüşme penceresi bırakın, sonra eskisini iptal edin.
Anahtarın kendisini de doğrulayın — bozuk bir private ile canlıya çıkmak, sızıntı kadar can sıkıcıdır:
openssl rsa -in mail.private -check -noout # "RSA key ok"Operasyonel kontrol listesi
- DKIM özel anahtarları
600, içinde bulundukları dizin700, sahiplikopendkim:opendkim—find /etc/opendkim/keys -type f ! -perm 600 -lsboş dönmeli. - Postfix
sasl_passwdvesasl_passwd.dbikisi de600,root:root. .git/klasörü web kökünden erişilebilir değil; sunucu/.gityollarını403döndürüyor.- Git geçmişi
gitleaks detect/trufflehogile taranıyor, CI'da kırılıyor.


