DKIM Anahtar Rotasyonu: İki Selector ile Sıfır Kesintili Değişim Runbook'u
DKIM anahtarını "sil, yenisini koy" diye döndüren herkes en az bir kez dünya çapında dkim=fail yaşadı. Doğrusu değiştirmek değil, örtüştürmek: iki selector'ı yan yana yaşatıp imzalamayı kaydırmak ve kuyruk penceresi dolana kadar eskisini canlı tutmak. Gün gün, komut komut operasyonel bir defter.
EvilMail Team17 Temmuz 202610 dk okuma
İki milyon adres barındıran bir mail altyapısında default._domainkey kaydını yeni bir p= ile üzerine yazıp OpenDKIM'i restart ettiğinizde şunu görürsünüz: yaklaşık bir saat boyunca gelen kutularını değil, spam klasörlerini doldurursunuz. Sebep basit ama acımasız. DNS'teki TXT kaydının TTL'i (çoğu kurulumda 3600 saniye) dolana kadar dünyanın yarısındaki resolver'lar hâlâ eski public key'i döner. İmzalayıcınız ise çoktan yeni private key'e geçmiştir. İki taraf uyuşmaz, imza matematiksel olarak tutmaz, verifier dkim=fail yazar.
Ters yönü de aynı ölçüde kötüdür: önce DNS'i güncelleyip imzalayıcıyı geç reload ederseniz, bu sefer yeni public key yayında ama postalar hâlâ eski private key ile imzalanır. Yine fail.
Buradaki asıl ders şu: DKIM'de anahtar rotasyonu asla bir "değiştirme" işlemi değildir, bir "örtüştürme" işlemidir. Kapatmanız gereken iki ayrı boşluk var ve ikisi de tek selector ile kapanmaz:
Propagation gap — DNS kaydı ile imzalayıcının anlaşmadığı, TTL ve yayılma kaynaklı pencere.
DKIM Anahtar Rotasyonu: İki Selector ile Kesintisiz Değişim — EvilMail Blog
Queue gap — eski anahtarla imzalanmış ve hâlâ kuyrukta bekleyen postaların, günler sonra alıcıya ulaştığında doğrulanabilir kalması gereken pencere.
Tek selector'ı üzerine yazdığınızda bu iki pencereden en az biri her zaman açık kalır. Çözüm, iki farklı selector'ı aynı anda DNS'te yaşatmaktır.
İki selector mantığı: rotasyon bir değiştirme değil, örtüşmedir
Selector, bir domain altında birden fazla DKIM anahtarını birbirinden ayıran etikettir. s2026q1._domainkey.evilmail.pro ve s2026q3._domainkey.evilmail.pro yan yana, hiçbir çakışma olmadan durabilir. Verifier, gelen mailin DKIM-Signature header'ındaki d= (domain) ve s= (selector) etiketlerini okur ve tam olarak o kaydı sorgular. İki kayıt aynı anda yayındayken hangi mailin hangi anahtarla imzalandığı sorun değildir — her mail kendi selector'ını taşır.
Bu, rotasyonu üç faza böler:
1.Yeni selector'ı DNS'e yayınla, ama henüz onunla imzalama.
2.İmzalamayı yeni selector'a kaydır, eski DNS kaydını olduğu yerde bırak.
3.Kuyruk boşalınca eski selector'ı emekliye ayır.
İsimlendirmede tek kural var: tarih tabanlı deterministik bir şema kullanın ve bir selector adını asla geri dönüştürmeyin.s2026q3, s2027q1 gibi. Eski bir selector adını (ör. default) yeni bir anahtarla tekrar kullanırsanız, cache'te kalmış eski public key ile yeni imzalar çakışır — tam da kaçtığınız arızaya geri dönersiniz.
Faz 0 — Hazırlık: TTL'i düşür, yeni anahtarı üret
Rotasyonun en sık atlanan adımı budur ve atlanırsa tüm faz geçişleriniz saatlerce sürer. Rotasyona başlamadan en az eski TTL kadar önce (yani en az bir saat, tercihen bir gün önce) DKIM TXT kayıtlarının TTL'ini 3600'den 300'e indirin. Böylece Faz 1 ile Faz 2 arasındaki bekleme 1 saat yerine 5 dakikaya iner. Rotasyon bitince 3600'e geri çekersiniz; düşük TTL'i kalıcı bırakmanın anlamı yok, boşuna sorgu yükü yaratır.
Ardından yeni anahtarı üretin:
bash
cd /etc/opendkim/keys/evilmail.pro
opendkim-genkey -b 2048 -s s2026q3 -d evilmail.pro -v
chown opendkim:opendkim s2026q3.private
chmod 600 s2026q3.private
# s2026q3.txt → DNS'e girilecek public kısım
# s2026q3.private → sunucuda kalan gizli kısım
Anahtar boyutu için tavsiye net: RSA-2048 mutlak asgari. 1024-bit artık M3AAWG tarafından deprecate edilmiş durumda ve birçok büyük alıcı onu zayıf sayıyor. Dikkat edilecek nokta: 2048-bit bir p= değeri, DNS'in 255 karakterlik tek TXT string sınırını aşar. BIND ve çoğu DNS sağlayıcısında bunu tırnaklı çoklu string olarak bölmeniz gerekir (parçalar birleştirilerek tek değer olarak okunur):
Faz 1 — Yeni selector'ı yayınla ve gerçekten doğrula
Kaydı ekledikten sonra, imzalamaya geçmeden önce üç doğrulama yapın. Bu fazda beklemenizin sebebi yeni TTL değil, kaydın tüm otoritatif NS'lere ve ara cache'lere yayılmasıdır.
bash
# 1) Kayıt yayında mı
dig +short TXT s2026q3._domainkey.evilmail.pro
# 2) Tüm otoritatif NS'ler aynı cevabı veriyor mu
for ns in storm void kraken pandora; do
echo "== $ns =="
dig @$ns.example.net TXT s2026q3._domainkey.evilmail.pro +short
done
# 3) private/public çifti gerçekten eşleşiyor mu
opendkim-testkey -d evilmail.pro -s s2026q3 -vvv
# beklenen: "key OK"
Dört NS'in dördü de aynı p= değerini dönmeden ve opendkim-testkey "key OK" demeden bir sonraki faza geçmeyin. testkey "key not secure" derse panik yapmayın; bu yalnızca DNSSEC uyarısıdır. Asıl beklediğiniz satır "key OK"tur.
Faz 2 — İmzalamayı yeni selector'a kaydır
Şimdi imzalayıcıyı çeviriyoruz. Eski DNS kaydına dokunmuyoruz; o, Faz 3'e kadar canlı kalacak.
OpenDKIM tarafında KeyTable'a yeni anahtarı ekleyip SigningTable'ı yeni selector'a yönlendirin:
bash
# /etc/opendkim/KeyTable
s2026q3._domainkey.evilmail.pro evilmail.pro:s2026q3:/etc/opendkim/keys/evilmail.pro/s2026q3.private
# /etc/opendkim/SigningTable — bu satırı yeni selector'a çevir
*@evilmail.pro s2026q3._domainkey.evilmail.pro
Sonra reload, restart değil — restart açık bağlantıları koparıp anlık bir imzalama boşluğu yaratabilir:
bash
systemctl reload opendkim
rspamd kullanıyorsanız burası ikinci selector'ı fırsata çevirmenin tam yeri. RSA-2048'in yanına bir Ed25519 anahtarı ekleyip çift algoritmalı imzalamaya geçebilirsiniz. Ed25519 imzaları çok daha kısadır; p= değeri tek TXT segmentine rahat sığar (k=ed25519; p=<32 byte base64>). Verifier tarafında Ed25519 desteği hâlâ evrensel olmadığı için RSA'yı bırakmadan yanına eklersiniz: uyumlu alıcı iki imzayı da görür, eski alıcı RSA'yı doğrular. Ed25519 anahtarını rspamadm dkim_keygen -t ed25519 -s s2026q3e -d evilmail.pro -k ... ile üretip public kısmını ayrı bir selector olarak yayınlarsınız:
s= etiketi artık s2026q3 gösteriyorsa ve Gmail PASS diyorsa, imzalama gerçekten çevrilmiştir.
Faz 3 — Eski selector'ı emekliye ayır: kuyruk penceresi
İşte en kritik zamanlama kararı. Eski selector, o anahtarla imzalanmış son mail kuyruktan çıkana kadar DNS'te kalmalı. Postfix'te bunu belirleyen maximal_queue_lifetime parametresidir ve varsayılanı 5 gündür:
Yani Faz 2'de imzalamayı çevirdikten sonra eski kaydı en az 5, güvenli tarafta 7 gün canlı tutun. Bir alıcı sunucu geçici olarak down olur ve mailiniz 4 gün kuyrukta beklerse, o mail nihayet teslim edildiğinde hâlâ eski s2026q1 kaydını bulup doğrulayabilmeli.
Süre dolunca iki seçeneğiniz var:
Kaydı tamamen silmek — sade, temiz.
Boş public key ile revoke etmek — RFC 6376 uyarınca p= değerini boş bırakmak, "bu anahtar iptal edildi" sinyalidir:
dns
s2026q1._domainkey.evilmail.pro. 300 IN TXT "v=DKIM1; k=rsa; p="
Rutin, hijyenik rotasyonda ikisi de kabul edilebilir. Ama fark, anahtar sızması durumunda ortaya çıkar. Private key sızdıysa kuyruk penceresini beklemezsiniz; derhal p= ile revoke edersiniz. Evet, bu kuyrukta bekleyen kendi meşru postalarınızın da fail etmesine yol açar; ancak sızmış bir anahtarla üçüncü tarafların sizin adınıza imzalı mail atmasının bedeli çok daha ağırdır. Rutin rotasyon "bekle", sızma "hemen kes" demektir.
Bunu takvime bağla: 6 ayda bir hijyenik rotasyon
NIST ve M3AAWG periyodik rotasyon önerir; makul bir kadans 6 ayda birdir. Çeyreklik selector şeması (s2026q1, s2026q3) bunu doğal olarak dosyalar. Süreci yarı otomatikleştiren bir iskelet şöyle görünür:
bash
#!/usr/bin/env bash
# rotate-dkim.sh — insan onay kapılı
set -euo pipefail
SEL="s$(date +%Y)q$(( ($(date +%-m)-1)/3+1 ))"
opendkim-genkey -b 2048 -s "$SEL" -d evilmail.pro
chown opendkim:opendkim "$SEL.private"; chmod 600 "$SEL.private"
push_txt_to_dns "$SEL" "$(cat "$SEL.txt")" # DNS API'nize göre
echo "DNS yayınlandı. dig + testkey doğrulayıp ENTER'a bas..."
read -r # <-- insan onay kapısı
switch_signer_selector "$SEL"
echo "İmzalama çevrildi. 7 gün sonra eskiyi revoke et."
Tam otomasyondan kasıtlı olarak kaçınıyoruz. dig doğrulaması ile imzalayıcı geçişi arasına bir read koymak, "DNS gerçekten dört NS'in dördünde de yayıldı mı" sorusunu bir insana sordurur. DKIM rotasyonunda hatanın bedeli saatlerce süren teslimat kaybıdır; buraya bir insan gözü koymak ucuz bir sigortadır.
Rotasyon kontrol listesi
[ ] Rotasyondan ≥1 saat önce DKIM TXT TTL'ini 3600 → 300 indir
[ ] opendkim-genkey -b 2048 ile yeni tarihli selector üret (s2026q3)
[ ] .private dosyası izinleri 600, sahibi opendkim:opendkim
[ ] Yeni TXT'i DNS'e yayınla (2048-bit ise tırnaklı çoklu string)
[ ] KeyTable/SigningTable'ı yeni selector'a çevir, reload (restart değil)
[ ] Test maili at, header'da s=s2026q3 ve alıcıda DKIM PASS doğrula
[ ] Eski selector'ı DNS'te 5–7 gün canlı tut (kuyruk penceresi)
[ ] Süre dolunca eskiyi sil veya p= ile revoke et
[ ] TTL'i 300 → 3600 geri yükselt
[ ] Sızma varsa: kuyruğu bekleme, derhal p= ile revoke et
İki boşluğu ayrı ayrı kapattığınız sürece — propagation gap'i örtüşme penceresiyle, queue gap'i maximal_queue_lifetime beklemesiyle — rotasyon boyunca tek bir mail bile dkim=fail görmez. Anahtarınızı değiştirmiyorsunuz; eskisinin üstüne yenisini yaşatıp, kuyruğun son nefesi çıkınca eskisini uğurluyorsunuz.