DomainHealthChecker: E-Posta Güvenlik Kayıtlarını PowerShell ile Toplu Denetlemek

Merhaba,

Bir kurumun e-posta güvenliği, aslında birkaç DNS kaydında saklıdır. SPF, DKIM ve DMARC doğru yapılandırılmamışsa; alan adınız adına sahte e-posta gönderilmesi teknik olarak son derece kolaydır. Buna DNSSEC, MTA-STS, TLS-RPT ve CAA kayıtlarını da eklediğinizde, tek bir domain için kontrol edilmesi gereken yedi sekiz farklı kayıt ortaya çıkıyor.

Bunları web tabanlı araçlarla tek tek kontrol etmek mümkün, ancak 40-50 domainli bir kurumsal envanterde bu iş hızla katlanılmaz hale geliyor. İşte tam bu noktada Martien van Dijk tarafından geliştirilen DomainHealthChecker PowerShell modülü devreye giriyor.

Bu yazıda modülü tanıtacak, her bir cmdlet’i örnekleriyle göstereceğim. Sonunda da toplu tarama yapıp HTML + CSV rapor üreten kendi wrapper script’imi paylaşacağım.

Neden Bu Kayıtlar Önemli?

Kayıtlara girmeden önce kısa bir hatırlatma yapalım. Aşağıdaki tabloda her kaydın ne işe yaradığını özetledim:

Kayıt Açılımı Ne Yapar Yoksa Ne Olur?
SPF Sender Policy Framework Domain adına e-posta göndermeye yetkili sunucuları listeler Herkes sizin adınıza mail gönderebilir
DKIM DomainKeys Identified Mail Giden mesajlara kriptografik imza ekler Mesaj içeriğinin değiştirilmediği doğrulanamaz
DMARC Domain-based Message Authentication, Reporting and Conformance SPF/DKIM’den geçemeyen mesajlara ne yapılacağını söyler SPF ve DKIM pratikte yaptırımsız kalır
DNSSEC Domain Name System Security Extensions DNS cevaplarını kriptografik olarak imzalar DNS cache poisoning riski
MTA-STS Mail Transfer Agent Strict Transport Security Sunucular arası TLS bağlantısını zorunlu kılar TLS downgrade ve MITM saldırıları
TLS-RPT TLS Reporting TLS teslimat hatalarını rapor eder Şifreleme hataları görünmez kalır
BIMI Brand Indicators for Message Identification Kurum logosunun posta kutusunda görünmesini sağlar Marka görünürlüğü ve güven kaybı
CAA Certification Authority Authorization Domain için sertifika kesebilecek CA’ları sınırlar Yetkisiz CA’dan sertifika alınabilir

Buradaki kritik nokta şu: bu kayıtlar birbirinden bağımsız değil. DMARC olmadan SPF ve DKIM birer tavsiye niteliğinde kalır. BIMI ise p=quarantine veya p=reject seviyesinde çalışan bir DMARC politikası olmadan zaten devreye girmez. Dolayısıyla hepsini birlikte görmek gerekir.

Modül Hakkında

Yazı tarihi itibarıyla modülün güncel sürümü 2.2.0. Bilmeniz gereken teknik detaylar:

Adım 0: PowerShell Sürümünüzü Öğrenin

Hangi PowerShell sürümünde olduğunuzu kontrol etmek için:

$PSVersionTable.PSVersion

Çıktı bu şekildeyse Windows PowerShell 5.1 üzerindesiniz demektir:

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      26100  33158

Bu bilgiyi aklınızda tutun; birazdan Install-PSResource bölümünde neden hata alacağınızı bu belirleyecek.

Yöntem 1: PowerShell Gallery (Önerilen)

Hem PowerShell 5.1 hem PowerShell 7.x üzerinde çalışan, en pratik yol. PowerShell’i Run as administrator (Yönetici olarak çalıştır) ile açıp:

Install-Module -Name DomainHealthChecker -Scope CurrentUser

Temiz bir sistemde ilk kurulumda PowerShell önce NuGet provider’ının kurulması için onay isteyecek. PowerShellGet modülü, NuGet tabanlı depolarla konuşabilmek için en az 2.8.5.201 sürümüne ihtiyaç duyuyor. Y diyerek devam ediyoruz:

Ardından ikinci bir onay ekranı geliyor: PSGallery varsayılan olarak untrusted repository (güvenilmeyen depo) olarak işaretlidir. Bu, modüle özgü bir güvenlik uyarısı değil, PSGallery’nin genel varsayılanıdır. Yine Y ile onaylıyoruz:

Kurulum sessizce tamamlanıyor ve prompt geri dönüyor:

Bu onay ekranlarını her seferinde görmek istemiyorsanız, PSGallery’yi güvenilir olarak işaretleyebilirsiniz. Ancak bunu bilinçli bir tercih olarak yapın; ondan sonra bu depodan kuracağınız her modül uyarısız kurulacaktır:

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

Kurulum tamamlandıysa doğrudan “Kurulumu Doğrulama” bölümüne geçebilirsiniz. Aşağıdaki iki yöntem alternatiftir, zorunlu değildir.

Yöntem 2: PSResourceGet ile Kurulum

Modülün dokümantasyonunda Install-PSResource ile kurulum da öneriliyor. Ancak bu komutu Windows PowerShell 5.1 üzerinde çalıştırırsanız hata alırsınız:

Install-PSResource -Name DomainHealthChecker/code>

Hatanın nedeni basit: Install-PSResourceMicrosoft.PowerShell.PSResourceGet modülüyle gelir ve bu modül Windows PowerShell 7.4 ve sonrasında kutudan çıkar. PowerShell 5.1’de varsayılan olarak bulunmaz. Adım 0’da 5.1 gördüyseniz bu hata beklenen davranıştır.

İki seçeneğiniz var. Ya klasik Install-Module ile devam edersiniz (5.1’de sorunsuz çalışır), ya da PSResourceGet’i ayrıca kurarsınız:

Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -Force

Kurulum çıktı üretmeden tamamlanır ve prompt geri döner:

Artık Install-PSResource komutu kullanılabilir:

Install-PSResource -Name DomainHealthChecker

İsteğe Bağlı: PowerShell 7 Kurulumu

Modül 5.1’de sorunsuz çalışıyor, dolayısıyla sırf bu modül için PowerShell 7 kurmak zorunda değilsiniz. Ancak Install-PSResource gibi modern komutları kullanmak ya da modülü Linux/macOS tarafında da çalıştırmak istiyorsanız PowerShell 7’ye geçmek mantıklı.

En pratik kurulum yolu winget:

winget install --id Microsoft.PowerShell --source winget

winget paketi bulur, installer hash’ini doğrular ve kurulumu tamamlar. Bu yazının hazırlandığı sırada gelen sürüm 7.6.4 idi:

Burada dikkat çeken nokta şu: kurulum bittiğinde mevcut PowerShell pencereniz kendiliğinden 7’ye dönüşmez. Yeni bir terminal açıp pwsh komutuyla başlatmanız gerekiyor. Windows Terminal kullanıyorsanız yeni sekme menüsünde “Windows PowerShell” (5.1) ve “PowerShell 7” ayrı ayrı listelenir.

Doğrulama:

pwsh -NoLogo -Command '$PSVersionTable.PSVersion'

Çıktıda Major 7 / Minor 6 / Patch 4 görünüyor. Windows PowerShell’in Major/Minor/Build/Revision şemasından farklı olarak PowerShell 7 semantik sürümleme kullanır; bu da hangi sürümde olduğunuzu bir bakışta ayırt etmenizi sağlar.

İki sürüm yan yana çalışır, PowerShell 7 kurulumu 5.1’i kaldırmaz. Ancak modül dizinleri ayrıdır:

Sürüm Modül Dizini
Windows PowerShell 5.1 Documents\WindowsPowerShell\Modules
PowerShell 7.x Documents\PowerShell\Modules

Bu ayrım önemli: 5.1’de kurduğunuz DomainHealthChecker modülü PowerShell 7’de otomatik olarak görünmez. pwsh içinde Install-Module -Name DomainHealthChecker -Scope CurrentUser komutunu bir kez daha çalıştırmanız gerekir.

Yani 5.1’de kurduğunuz DomainHealthChecker modülü PowerShell 7’de otomatik olarak görünmez; pwsh içinde bir kez daha Install-Module çalıştırmanız gerekir.

Yöntem 3: GitHub’dan Manuel Kurulum

PowerShell Gallery’ye erişimin kısıtlı olduğu ortamlarda modülü GitHub üzerinden klonlayıp elle import edebilirsiniz.

Önkoşul: Git

Temiz bir Windows sunucusunda Git varsayılan olarak gelmez. Kurulu değilse git clone komutu şu hatayı verir:

git : The term 'git' is not recognized as the name of a cmdlet, function, script file, or operable program.

Burada dikkat çeken nokta şu: bu hatayı gördükten sonra ardından çalıştıracağınız cd ve Import-Module komutları da hata verir. Bu ikisi ayrı sorunlar değildir; klasör hiç oluşmadığı için zincirleme oluşan sonuçlardır. Yani düzeltilmesi gereken tek bir sorun var: Git eksik.

Git’i winget ile kurun:

winget install --id Git.Git --source winget

Kurulumdan sonra terminali kapatıp yeniden açın. PATH değişikliği mevcut oturuma yansımaz; aynı pencerede devam ederseniz git komutu hala tanınmaz.

Yeni terminalde doğrulayın:

git --version

Sürüm numarası göründüyse klonlamaya hazırsınız.

Klonlama ve Import

Depoyu nereye indireceğinizi kontrol etmek için önce hedef dizine geçin, sonra klonlayın. Bu yazıdaki wrapper script’i de içeren kendi depomu kullanmak isterseniz:

cd $env:USERPROFILE\Documents
git clone https://github.com/bakicubuk/Domain_SpfDkimDmarc_Checker.git
cd .\Domain_SpfDkimDmarc_Checker\

Klasörün içindeyken manifest dosyasının yerini doğrulayın:

Get-ChildItem -Filter *.psd1 -Recurse | Select-Object FullName

Manifest kök dizinde değilse Import-Module yolunu ona göre düzeltin. Import etmeden önce manifest’i doğrulamak iyi bir alışkanlıktır, özellikle kendi fork’unuzu ya da başkasının deposunu kullanıyorsanız, çalıştırdığınız kodun ne olduğunu bilerek import edin:

Test-ModuleManifest .\DomainHealthChecker.psd1
Import-Module .\DomainHealthChecker.psd1 -Force -Verbose

Kurulum Sayısını Doğrulama

Son olarak kaç fonksiyonun geldiğini sayın:

Get-Command -Module DomainHealthChecker | Measure-Object | Select-Object Count

Kurulum Listesini Doğrulama

Hangi yöntemi kullandıysanız kullanın, modülün düzgün yüklendiğini tek komutla teyit edin:

Get-Command -Module DomainHealthChecker

Listede 10 fonksiyon görüyorsanız kurulum tamamdır. Komut boş dönerse modül import edilmemiş demektir; manuel kurulum yaptıysanız Import-Module adımını atlamış olabilirsiniz.

10 görüyorsanız kurulum tamamdır, doğrudan kullanmaya başlayabilirsiniz:

Invoke-SpfDkimDmarc -Name bakicubuk.com

Daha az fonksiyon geliyorsa public klasörü eksik ya da dosyalar hala bloklu olabilir. Bu durumda bir sonraki bölümdeki teşhis adımlarını izleyin.

Git Kurmadan: ZIP ile İndirme

Sunucuya Git kurmak istemiyorsanız ya da kurulum izniniz yoksa, depoyu ZIP olarak indirip açabilirsiniz:

$Zip = "$env:TEMP\dhc.zip"
Invoke-WebRequest -Uri "https://github.com/bakicubuk/Domain_SpfDkimDmarc_Checker/archive/refs/heads/main.zip" -OutFile $Zip
Expand-Archive -Path $Zip -DestinationPath $env:USERPROFILE\Documents -Force
Remove-Item $Zip

Burada dikkat çeken nokta şu: ZIP ile indirdiğinizde klasör adının sonuna dal adı eklenir. Yukarıdaki komutta klasör Domain_SpfDkimDmarc_Checker değil, Domain_SpfDkimDmarc_Checker-main olarak açılır. Klonlama ile indirdiğinizde bu ek olmaz. Sonraki komutlarda doğru ismi kullanmanız gerekir.

Klasöre geçip içeriği kontrol edelim:

cd $env:USERPROFILE\Documents\Domain_SpfDkimDmarc_Checker-main
Get-ChildItem

Listede DomainHealthChecker.psd1DomainHealthChecker.psm1 dosyalarıyla birlikte public ve private klasörlerini görmelisiniz. Bu iki klasör kritik: fonksiyonların asıl kodu orada bulunur ve eksik olurlarsa modül sessizce yalnızca tek fonksiyon yükler.

İnternetten indirilen dosyalar Windows tarafından işaretlenir ve çalıştırılmaları engellenebilir. ZIP yöntemiyle indirdiyseniz bu adımı atlamayın:

Get-ChildItem -Recurse | Unblock-File

Bu komut hata vermez ve çıktı üretmez; engeli sessizce kaldırır. Atlanırsa modül import edilirken anlaşılması güç hatalar alırsınız.

Ardından manifest’i doğrulayıp modülü yükleyin:

Test-ModuleManifest .\DomainHealthChecker.psd1
Import-Module .\DomainHealthChecker.psd1 -Force -Verbose

Son olarak kaç fonksiyonun geldiğini sayın:

Get-Command -Module DomainHealthChecker | Measure-Object | Select-Object Count

10 görüyorsanız kurulum tamamdır, doğrudan kullanmaya başlayabilirsiniz:

Invoke-SpfDkimDmarc -Name bakicubuk.com

Daha az fonksiyon geliyorsa public klasörü eksik ya da dosyalar hala bloklu olabilir. Bu durumda bir sonraki bölümdeki teşhis adımlarını izleyin.

Sonraki Oturumlarda Kullanım

Kurulum bitti, terminali kapattınız. Ertesi gün bir domain kontrol etmek istediğinizde ne olacak? Cevap, hangi yöntemle kurduğunuza bağlı.

PowerShell Gallery ile kurduysanız ek bir şey yapmanız gerekmez. Yeni pencere açıp doğrudan komutu çalıştırabilirsiniz:

Get-SPFRecord -Name bakicubuk.com

Modül standart modül dizininde olduğu için PowerShell onu ilk kullanımda otomatik yükler.

Manuel kurulum yaptıysanız (klonlama ya da ZIP) modül standart dizinde değildir, dolayısıyla her yeni oturumda import etmeniz gerekir:

Import-Module $env:USERPROFILE\Documents\Domain_SpfDkimDmarc_Checker\DomainHealthChecker.psd1 -Force
Get-SPFRecord -Name bakicubuk.com

Bunu her seferinde yazmak istemiyorsanız iki kalıcı çözüm var.

Yöntem 1: Modülü standart dizine kopyalayın

$Hedef = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\DomainHealthChecker"
New-Item -Path $Hedef -ItemType Directory -Force
Copy-Item "$env:USERPROFILE\Documents\Domain_SpfDkimDmarc_Checker\*" -Destination $Hedef -Recurse -Force

Burada dikkat çeken nokta şu: hedef klasörün adı modül adıyla aynı olmak zorundadır, yani DomainHealthChecker. Depo adıyla (Domain_SpfDkimDmarc_Checker) kopyalarsanız PowerShell modülü bulamaz. Bu, otomatik yüklemenin çalışması için gereken tek kuraldır.

PowerShell 7 kullanıyorsanız hedef dizin farklıdır:

$Hedef = "$env:USERPROFILE\Documents\PowerShell\Modules\DomainHealthChecker"

Kopyalama sonrası yeni bir pencerede doğrudan çalışır:

Get-SPFRecord -Name bakicubuk.com

Yöntem 2: Profil dosyasına ekleyin

Depoyu geliştirme kopyası olarak kullanmaya devam etmek isterseniz, import komutunu PowerShell profilinize ekleyebilirsiniz. Profil, her oturum açılışında otomatik çalışan bir script’tir:

if (-not (Test-Path $PROFILE)) { New-Item -Path $PROFILE -ItemType File -Force }
notepad $PROFILE

Açılan dosyaya şu satırı ekleyip kaydedin:

Import-Module "$env:USERPROFILE\Documents\Domain_SpfDkimDmarc_Checker\DomainHealthChecker.psd1"

Yeni pencere açtığınızda modül hazır olur. Depoda git pull ile güncelleme yaptığınızda değişiklikler doğrudan yansır; kopyalama yöntemindeki gibi tekrar kopyalamanız gerekmez.

Açıklama Standart Dizine Kopyalama Profile Ekleme
Otomatik yükleme Evet Evet
Güncelleme sonrası ek işlem Gerekir (tekrar kopyala) Gerekmez
Uygun olduğu durum Sadece kullanacaksanız Depoda değişiklik yapacaksanız

Kurulumun Hala Geçerli Olduğunu Doğrulama

Aradan zaman geçtiyse ve modülün hala düzgün çalıştığından emin olmak isterseniz üç komut yeterli:

Get-Module DomainHealthChecker -ListAvailable | Select-Object Name, Version, ModuleBase
Get-Command -Module DomainHealthChecker | Measure-Object | Select-Object Count
Get-SPFRecord -Name bakicubuk.com

Birincisi modülün hangi dizinden yüklendiğini gösterir, birden fazla kopyanız varsa hangisinin kullanıldığını buradan anlarsınız. İkincisi 10 dönmelidir. Üçüncüsü ise gerçek bir sorgu yapar; DNS tarafında da sorun olmadığını teyit eder.

Kopyala-yapıştır konusunda son bir not: komutları web sayfasından alıyorsanız satır sonlarına görünmez kontrol karakterlerinin (^D gibi) bulaşabildiğini unutmayın. PowerShell bunları komutun parçası sayar ve anlaşılmaz hatalar üretir. Şüpheli bir durumda komutu elle yazmak en hızlı çözümdür.

Modüldeki Cmdlet’ler ve Alias’lar

Modül 10 fonksiyon export ediyor ve her biri için kısa bir alias tanımlı:

Cmdlet Alias Ne İşe Yarar
Invoke-SpfDkimDmarc isdd Tüm kontrolleri tek seferde çalıştırır
Get-SPFRecord gspf SPF kaydı, uzunluğu ve DNS lookup sayısı
Get-DKIMRecord gdkim DKIM kaydı ve selector tespiti
Get-DMARCRecord gdmarc DMARC politikası
Get-DNSSEC gdnssec DNSSEC durumu
Invoke-MtaSts gmts MTA-STS yapılandırması
Get-BimiRecord gbimi BIMI kaydı
Get-TlsRpt gtlstps TLS-RPT kaydı
Get-CAARecord gcaa CAA kaydı
Update-ModuleDomainHealthChecker Modülü günceller

Invoke-SpfDkimDmarc için ayrıca Show-SpfDkimDmarc alias’ı da tanımlı.

Cmdlet’ler Tek Tek

Get-SPFRecord

Get-SPFRecord -Name bakicubuk.com

Bu cmdlet’in en değerli tarafı, sadece kaydı göstermekle kalmaması. Çıktıda SPFRecordLength ve SPFRecordDnsLookupCount alanları da dönüyor. İkincisi özellikle önemli: SPF standardı en fazla 10 DNS lookup’a izin verir. İç içe geçmiş include: ifadeleriyle bu sınırı aşan kayıtlar permerror üretir ve pratikte tamamen geçersiz hale gelir. Modül bu sayımı sizin için yapıyor.

SPF Lookup Sayacı Ne Anlatıyor?

SPFRecordDnsLookupCount alanı, kaydı çözümlerken kaç DNS sorgusu yapılması gerektiğini söyler. RFC 7208, bu sayının 10’u aşmasını yasaklar; aşan kayıtlar alıcı sunucuda permerror üretir ve SPF pratikte hiç yokmuş gibi davranır.

Modül bu sayımı sizin için yapıyor ve sonucu üç farklı metinden biriyle döndürüyor:

Çıktı Anlamı Ne yapmalı?
7/10 (OK) 8 ve altı lookup. Sınırın güvenli tarafındasınız. Bir şey yapmanıza gerek yok
9/10 (Ok, but watch your DNS Lookups!) 9 ve üzeri. Kayıt hala geçerli ama sınıra yaklaştınız. Yeni servis eklemeden önce mevcut include: ifadelerini gözden geçirin
10/10 (Ok, but maximum DNS Lookups reached!) Tam sınırdasınız. Ekleyeceğiniz bir tek mekanizma kaydı geçersiz kılar. Konsolidasyon yapın (aşağıya bakın)

Soldaki sayı gerçek lookup sayısı, sağdaki 10 ise RFC sınırıdır. Yani 7/10 , “on hakkın yedisini kullandın” demektir.

Burada dikkat çeken bir davranış var: modül lookup sayısı 11 veya daha fazla olduğunda ayrı bir mesaj üretmiyor; bu durumda çıktı 11/10 (Ok, but watch your DNS Lookups!) şeklinde geliyor. Yani metinde “Ok” yazması kaydın geçerli olduğu anlamına gelmez. Sayının kendisine bakmak gerekir: soldaki değer 10’u geçtiyse SPF kaydınız zaten bozuktur.

Hangi mekanizmalar sayılıyor? include:aa:mxmx: ve ptr. Modül include: ifadelerini özyinelemeli olarak takip eder, yani içeride başka include: varsa onları da sayar. ip4: ve ip6: ise DNS sorgusu gerektirmediği için sayılmaz, bu yüzden lookup sayısını düşürmenin en pratik yolu, mümkün olan yerlerde include: yerine doğrudan IP bloğu yazmaktır.

SpfAdvisory Alanı Ne Söylüyor?

Sayacın yanında SpfAdvisory alanı da metinsel bir değerlendirme döndürüyor. Ürettiği başlıca mesajlar:

Durum Advisory İçeriği (Özet)
Kayıt yok Domain’in SPF kaydı yok, kötüye kullanımı önlemek için eklenmeli
Birden fazla SPF kaydı RFC 4408’e göre domain başına yalnızca bir SPF kaydı olabilir
450+ karakter RFC 7208’e göre bundan kaçınılmalı (SHOULD)
Tek string’de 255+ karakter RFC 4408’e göre yapılmamalı (MUST NOT)
-all ile bitiyor Politika yeterince katı
~all ile bitiyor Kayıt var ama politika yeterince katı değil
?all veya +all Kayıt geçerli ama politika etkili değil
Qualifier yok Sonda all mekanizması bulunamadı, politika etkisiz

Birden fazla SPF kaydı olması sıkça karşılaşılan ve fark edilmesi zor bir hatadır: iki ayrı ekip iki ayrı TXT kaydı eklediğinde alıcı sunucular permerror verir ve her iki kayıt da işlevsiz kalır. Modülün bunu yakalaması, tek başına kullanmaya değer bir özellik.

Kaydın sonundaki mekanizmaya da dikkat edin: ~all (softfail) yerine -all (hardfail) kullanmak, spoofing denemelerine karşı çok daha net bir duruş sağlar.

Belirli bir DNS sunucusu üzerinden sorgulamak isterseniz -Server parametresi mevcut:

Get-SPFRecord -Name bakicubuk.com -Server 1.1.1.1

Get-DKIMRecord

Get-DKIMRecord -Name bakicubuk.com

DKIM’in zorluğu, public key’in bir selector adı altında yayınlanması ve bu selector’ün domain dışından tahmin edilmesi gerekmesidir. Modül yaygın selector’leri otomatik deniyor. Birden fazla selector tespit ederse çıktıda DkimSelectorsDetected alanı doluyor ve her selector için ayrı ayrı DkimSelector-1DkimRecord-1DkimSelector-2DkimRecord-2 şeklinde property’ler üretiliyor.

Kurumunuz standart dışı bir selector kullanıyorsa elle belirtebilirsiniz:

Get-DKIMRecord -Name bakicubuk.com -DkimSelector selector1

Exchange Online tarafında selector1 ve selector2 standarttır. Google Workspace ise varsayılan olarak google selector’ünü kullanır.

Get-DMARCRecord

Get-DMARCRecord -Name bakicubuk.com

Politikanın p=none seviyesinde bırakılması sahada gördüğüm en yaygın hata. Bu seviye sadece raporlama yapar, hiçbir mesajı engellemez. Doğru yaklaşım kademeli geçiştir: p=none ile başlayıp rua raporlarını birkaç hafta izleyin, meşru gönderici kaynaklarını SPF ve DKIM tarafında düzeltin, ardından p=quarantine, en sonunda p=reject seviyesine çıkın. pct= etiketiyle bu geçişi yüzdesel olarak da yapabilirsiniz.

Get-DNSSEC

Get-DNSSEC -Name bakicubuk.com

DNSSEC, DNS cevaplarına kriptografik imza ekleyerek cevabın gerçekten yetkili sunucudan geldiğini ve yolda değiştirilmediğini garantiler. E-posta güvenliğiyle doğrudan ilişkisi şudur: SPF, DKIM ve DMARC kayıtlarının hepsi DNS üzerinden okunur. DNS cevabı manipüle edilebiliyorsa, bu kayıtların hiçbirinin güvencesi kalmaz.

Invoke-MtaSts

Invoke-MtaSts -Name bakicubuk.com

MTA-STS iki parçadan oluşur: _mta-sts altındaki TXT kaydı ve mta-sts.domain.com adresinde HTTPS üzerinden sunulan policy dosyası. Cmdlet ikisini birlikte kontrol ediyor. Çıktıdaki mtaRecord alanı uzun olabildiği için Format-List ile okumak daha rahat:

Invoke-MtaSts -Name bakicubuk.com | Format-List

Get-TlsRpt

Get-TlsRpt -Name bakicubuk.com

TLS-RPT, MTA-STS’in tamamlayıcısıdır. Karşı taraftaki mail sunucusu, size mesaj gönderirken TLS bağlantısında sorun yaşarsa bunu belirttiğiniz adrese raporlar. MTA-STS’i enforce moduna almadan önce TLS-RPT ile bir süre gözlem yapmak, teslimat kaybı yaşamamak açısından kritik.

Get-BimiRecord

$Record = Get-BimiRecord -Name bakicubuk.com
$Record | Format-List

Kayıt bulunamadığında modül boş çıktı vermiyor; ne sorguladığını, kaydın bulunamadığını ve nasıl yapılandırılması gerektiğini BimiAdvisory alanında açıklıyor. BIMI’nin ön koşulunun p=quarantine veya p=reject seviyesinde DMARC olduğunu tekrar hatırlatayım.

Bu cmdlet çalışmıyorsa: Diğer komutlar sorunsuz çalışırken yalnızca bu komut şu hatayı veriyorsa

Get-BimiRecord : The term 'Get-BimiRecord' is not recognized as the name of a cmdlet...

modül aslında yüklüdür, ancak yüklenen kopya PowerShell Gallery’deki orijinal sürümdür. O sürümde public\Get-BimiRecord.ps1 dosyasının başında, .psm1 dosyasına ait bir modül yükleyici blok bulunuyor. Bu dosya alfabetik olarak ilk yüklenen public dosya olduğundan, söz konusu blok yükleme tamamlanmadan klasörü yeniden taramaya başlatıyor ve fonksiyonun modül kapsamında tanımlanmasını engelliyor. Sonuç olarak modül 10 yerine 9 komut sunuyor.

Hangi kopyanın yüklü olduğunu şöyle görebilirsiniz:

Get-Module DomainHealthChecker | Select-Object Name, Version, ModuleBase

ModuleBase değeri WindowsPowerShell\Modules\DomainHealthChecker gösteriyorsa Gallery kopyası devrededir. Bu depodaki sürümde ilgili blok kaldırılmıştır; düzeltilmiş kopyayı yüklemek için:

Remove-Module DomainHealthChecker -Force -ErrorAction SilentlyContinue
Import-Module $env:USERPROFILE\Documents\Domain_SpfDkimDmarc_Checker\DomainHealthChecker.psd1 -Force
Get-Command -Module DomainHealthChecker | Measure-Object | Select-Object Count

Sayı 10 dönüyorsa Get-BimiRecord artık kullanılabilir.

Kayıt bulunamadığında modül boş çıktı vermiyor; ne sorguladığını, kaydın bulunamadığını ve nasıl yapılandırılması gerektiğini BimiAdvisory alanında açıklıyor. BIMI’nin ön koşulunun p=quarantine veya p=reject seviyesinde DMARC olduğunu tekrar hatırlatayım.

Get-CAARecord

Get-CAARecord -Name bakicubuk.com

CAA doğrudan bir e-posta kaydı değil, ancak domain güvenliğinin bütününe dahil. Hangi sertifika otoritelerinin sizin domaininiz için sertifika kesebileceğini sınırlar. Mail sunucularınızın TLS sertifikaları da bu kapsamda. Bu cmdlet’in -Server parametresini desteklememesinin teknik bir nedeni var: PowerShell’in Resolve-DnsName komutu CAA kayıt tipini desteklemiyor. Bu yüzden modül CAA sorgusunu Cloudflare’in DNS over HTTPS resolver’ı üzerinden yapıyor. Split DNS ortamlarında bu ayrımı bilmekte fayda var.

Ayrıca cmdlet sadece kaydın varlığına bakmıyor, IODEF iletişim adresinin tanımlı olup olmadığını da kontrol ediyor. IODEF, yetkisiz bir sertifika talebi olduğunda size bildirim gitmesini sağlar.

Advisory Mesajları ve Türkçe Karşılıkları

Modülün ürettiği ...Advisory alanları İngilizcedir ve öyle kalmalıdır, bunlar kullanıcıya gösterilen açıklama değil, script’lerin ayrıştırdığı çıktı verisidir. Çevrilirse rapor üreten kodlar bozulur.

Aşağıdaki tablolar, modülün kaynak kodundan çıkarılmış tüm mesajları içerir. Kendi çıktınızdaki cümleyi burada bulup ne yapmanız gerektiğini görebilirsiniz.

SPF – SPFAdvisory

Çıktı Anlamı Yapılacak
An SPF-record is configured and the policy is sufficiently strict. Kayıt var, -all ile bitiyor Bir şey yapmanıza gerek yok
An SPF-record is configured but the policy is not sufficiently strict. Kayıt var ama ~all (softfail) Hazır olduğunuzda -all seviyesine geçin
Your domain has a valid SPF record but your policy is not effective enough. ?all (neutral) ya da +all (pass) kullanılmış +all herkese izin verir; acilen değiştirin
No qualifier found. Your domain has a SPF record but your policy is not effective enough. Kaydın sonunda all mekanizması yok Kaydın sonuna -all ekleyin
Domain does not have an SPF record. To prevent abuse of this domain, please add an SPF record to it. Kayıt hiç yok Mutlaka ekleyin; adınıza sahte mail atılabilir
Domain has more than one SPF record. Only one SPF record per domain is allowed. This is explicitly defined in RFC4408. Birden fazla SPF kaydı Acil: iki kayıt da geçersiz sayılır, tek kayda birleştirin
Your SPF-record has more than 450 characters. This is SHOULD be avoided according to RFC7208. Kayıt 450 karakteri aşıyor Kısaltmayı değerlendirin (uyarı seviyesi)
Your SPF record has more than 255 characters in one string. This MUST not be done as explicitly defined in RFC4408. Tek string 255 karakteri aşıyor Kural ihlali: kaydı birden fazla string’e bölün

Bu mesajlar birleşik gelebilir. Örneğin uzun ve katı bir kayıtta hem 255 karakter uyarısını hem “sufficiently strict” ifadesini aynı satırda görürsünüz.

SPF – SPFRecordDnsLookupCount

Bu alan üç varyanttan biriyle döner. Soldaki sayı gerçek lookup sayısı, sağdaki 10 ise RFC 7208 sınırıdır.

Çıktı Koşul Yapılacak
0/10 (OK)8/10 (OK) 8 ve altı Bir şey yapmanıza gerek yok
9/10 (Ok, but watch your DNS Lookups!) 9 ve üzeri Yeni servis eklemeden önce mevcut include: ifadelerini gözden geçirin
10/10 (Ok, but maximum DNS Lookups reached!) Tam 10 Ekleyeceğiniz tek bir mekanizma kaydı geçersiz kılar; konsolidasyon yapın

Burada dikkat çeken nokta şu: lookup sayısı 11 veya daha fazlaysa modül ayrı bir mesaj üretmez, çıktı 11/10 (Ok, but watch your DNS Lookups!) şeklinde gelir. Metinde “Ok” yazması kaydın geçerli olduğu anlamına gelmez, soldaki sayı 10’u geçtiyse SPF kaydınız permerror alıyor demektir.

Sayıya dahil olan mekanizmalar: include:aa:mxmx:ptrip4: ve ip6: DNS sorgusu gerektirmediği için sayılmaz, lookup düşürmenin en pratik yolu, mümkün olan yerlerde include: yerine doğrudan IP bloğu yazmaktır.

DKIM – DkimAdvisory

Çıktı Anlamı Yapılacak
DKIM-record found. Kayıt bulundu (tek selector) Bir şey yapmanıza gerek yok
DKIM-record found for selector . Belirtilen selector altında kayıt var Bir şey yapmanıza gerek yok
No DKIM-record found for selector ._domainkey. O selector altında kayıt yok Doğru selector’ü kullandığınızdan emin olun
We couldn't find a DKIM record associated with your domain. Denenen selector’lerin hiçbirinde kayıt yok Mail sağlayıcınızın selector’ünü öğrenip -DkimSelector ile deneyin

Exchange Online varsayılan olarak selector1 ve selector2 , Google Workspace ise google selector’ünü kullanır.

DMARC – DmarcAdvisory

Çıktı Anlamı Yapılacak
Domain has a DMARC record and your DMARC policy will prevent abuse of your domain by phishers and spammers. p=reject – en güçlü seviye Bir şey yapmanıza gerek yok
Domain has a valid DMARC record but the DMARC (subdomain) policy does not prevent abuse of your domain by phishers and spammers. p=none ya da p=quarantine Raporları izleyip kademeli olarak p=reject‘e çıkın
The subdomain policy prevent abuse of your domain by phishers and spammers. sp=reject – alt alan adları korunuyor Bir şey yapmanıza gerek yok
The subdomain policy does not prevent abuse of your domain by phishers and spammers. sp=none – alt alan adları korumasız sp=reject yapın; saldırganlar alt alan adlarını hedefler
The subdomain has a DMARC record and it is set to sp=quarantine. To prevent you subdomains configure the policy to sp=reject. sp=quarantine sp=reject seviyesine çıkın
Does not have a DMARC record. This domain is at risk to being abused by phishers and spammers. Kayıt yok Öncelikli: SPF ve DKIM bu kayıt olmadan yaptırımsız kalır

Ana politika (p=) ve alt alan politikası (sp=) mesajları birlikte gelebilir.

DNSSEC – DnsSecAdvisory

Çıktı Anlamı Yapılacak
Great! DNSSEC is enabled on your domain. Domain DNSSEC ile imzalı Bir şey yapmanıza gerek yok
Enable DNSSEC on your domain. DNSSEC decreases the vulnerability to DNS attacks. İmzalı değil Domain kayıt firmanızın panelinden etkinleştirin

DNSSEC alanı ayrıca Domain is DNSSEC signed. ya da No DNSKEY records found. değerini döndürür.

MTA-STS – mtaAdvisory

Bu cmdlet en fazla varyantı olan kontroldür; hem DNS kaydını hem politika dosyasını denetler.

Çıktı Anlamı Yapılacak
The domain has the MTA-STS DNS record and file configured and protected against interception or tampering. Her şey yolunda Bir şey yapmanıza gerek yok
The MTA-STS DNS record doesn't exist. _mta-sts TXT kaydı yok TLS zorunluluğu yok; downgrade saldırılarına açıksınız
There are multiple MTA-STS DNS records. Birden fazla kayıt var Fazlasını silin, tek kayıt bırakın
The MTA-STS id must be alphanumeric and no longer than 32 characters. id= değeri kurala uymuyor Alfanümerik ve en fazla 32 karakter olmalı
The MTA-STS file doesn't exist. DNS kaydı var ama politika dosyası yok https://mta-sts./.well-known/mta-sts.txt adresini yayınlayın
The MTA-STS version is not configured in the file. The only options is STSv1. Dosyada version satırı yok version: STSv1 ekleyin
The MTA-STS version is not configured properly. Only STSv1 is supported. Sürüm değeri hatalı STSv1 olarak düzeltin
The MTA-STS mode is not configured in the file. Options are Enforce, Testing and None. Dosyada mode satırı yok mode: enforce ekleyin
The MTA-STS file is configured in <Testing/None> mode and not protecting interception or tampering. Mod enforce değil Gözlem bittiyse enforce moduna geçin
The MTA-STS max age configured in the file should be greater than 604800 seconds and less than 31557600 seconds. max_age aralık dışında 1 hafta ile 1 yıl arasında bir değer verin
The MTA-STS file doesn't have any MX record configured. Dosyada mx satırı yok Mail sunucularınızı listeleyin
The MTA-STS file MX records don't match with the MX records configured in the domain. Dosyadaki MX listesi gerçek MX kayıtlarıyla uyuşmuyor Önemli: eksik ya da fazla girdi teslimat kaybına yol açar
At least one of the MX records configured in the MTA-STS file MX records list doesn't support TLS. MX sunucularından biri STARTTLS desteklemiyor O sunucuda TLS’i etkinleştirin

TLS-RPT – TlsRptAdvisory

Çıktı Anlamı Yapılacak
TLS-RPT Record found. The 'rua' field is configured. Kayıt var, raporlama açık Bir şey yapmanıza gerek yok
TLS-RPT Record found, but the 'rua' field is not configured. Consider adding a 'rua' field to receive reports. Kayıt var ama rapor adresi yok rua=mailto:... ekleyin
No TLS-RPT Record found. Consider configuring a TLS-RPT record for this domain, to receive reports. Kayıt yok MTA-STS’i enforce yapmadan önce bunu ekleyip gözlem yapın

BIMI – BimiAdvisory

BIMI değerlendirmesi iki parçadan oluşur: DMARC uyumluluğu ve VMC sertifikası. Her iki parçadan da birer mesaj gelir.

DMARC uyumluluğu:

Çıktı Anlamı Yapılacak
DMARC policy is set to p=reject, which is the best policy for BIMI to function. DMARC tarafı BIMI’ye hazır Logo ve BIMI kaydı ekleyebilirsiniz
DMARC policy is set to p=quarantine. While BIMI can function with this policy, it is recommended to use p=reject for better protection. Çalışır ama ideal değil p=reject seviyesine çıkın
DMARC policy is set to p=none. BIMI requires a DMARC policy of at least p=quarantine to function. BIMI çalışmaz Önce DMARC’ı sıkılaştırın
Does not have a DMARC record. To use BIMI, this domain must have a DMARC record with a policy of at least p=quarantine and pct=100. DMARC yok BIMI’den önce DMARC kurun
DMARC policy pct is set to less than 100%, BIMI requires a DMARC policy with pct=100 to function. pct= değeri 100’den küçük pct=100 yapın

VMC sertifikası (a= etiketi):

Çıktı Anlamı Yapılacak
'a=' (VMC) tag contains a valid HTTPS URL. Geçerli sertifika adresi Bir şey yapmanıza gerek yok
No 'a=' (VMC) tag found, it's recommended to include a VMC certificate. Sertifika tanımlı değil Zorunlu değil; bazı sağlayıcılar VMC’siz logo göstermez
'a=' (VMC) tag does not contain a valid HTTPS URL, it should start with 'https://'. Adres HTTPS ile başlamıyor Düzeltin; aksi halde logonuz kötüye kullanılabilir
VMC certificate is valid, expiration date: . Sertifika geçerli Tarihi takviminize alın
VMC certificate is expired, expiration date: . Please renew the certificate. Sertifika süresi dolmuş Yenileyin
Failed to download VMC certificate from URL: . Sertifika indirilemedi Adresin erişilebilir olduğunu kontrol edin

BimiRecord alanı ise kayıt yoksa We couldn't find a BIMI record associated with your domain. değerini döndürür.

CAA – CAAAdvisory

Çıktı Anlamı Yapılacak
CAA record found with IODEF contact information: <adres> Kayıt ve bildirim adresi tanımlı Bir şey yapmanıza gerek yok
CAA record found and IODEF not configured. Consider adding an IODEF contact to the CAA record to receive notifications. Kayıt var, bildirim adresi yok iodef= ekleyin; yetkisiz sertifika talebinde haberiniz olur
No CAA record found. Consider adding a CAA record specifying which CA(s) are authorized to issue certificates for <domain>. Kayıt yok Hangi CA’ların sertifika kesebileceğini sınırlayın

Bir Çıktı Anomalisi

BIMI kontrolünde şuna benzer bir satırla karşılaşabilirsiniz:

BimiAdvisory : No BIMI record found for domain.BIMI record found. No 'a=' (VMC) tag found...

Aynı cümlede hem “kayıt bulunamadı” hem “kayıt bulundu” yazıyor. Bunun nedeni BimiAdvisory alanının += ile biriktirilerek oluşturulması ve döngü içinde sıfırlanmamasıdır; önceki değerlendirmenin metni sonrakine ekleniyor.

Yorumlarken BimiRecord alanına bakın; asıl gerçeği o söyler. Yukarıdaki örnekte BimiRecord değeri “We couldn’t find a BIMI record…” olduğu için kayıt yoktur. Aynı birikme davranışı SPF ve MTA-STS tarafında da görülebilir, orada mesajlar birbirini tamamladığı için sorun yaratmaz.

Hepsini Tek Komutta: Invoke-SpfDkimDmarc

Asıl iş yükünü kaldıran cmdlet bu. Yukarıdaki sekiz kontrolü tek çağrıda çalıştırıp birleşik bir nesne döndürüyor:

Invoke-SpfDkimDmarc -Name bakicubuk.com

Birden fazla domaini doğrudan parametre olarak verebilirsiniz:

Invoke-SpfDkimDmarc -Name bakicubuk.com, ornek.com, test.com

Ya da bir dosyadan okutabilirsiniz:

Invoke-SpfDkimDmarc -File C:\Temp\domains.txt

Pipeline desteği de var:

Get-Content C:\Temp\domains.txt | Invoke-SpfDkimDmarc

Burada dikkat çeken nokta şu: cmdlet her çalıştırıldığında otomatik olarak modül güncelleme kontrolü yapıyor. Tek bir domain için sorun değil, ancak 50 domainlik bir listede bu gereksiz gecikme ve dış ağ trafiği demek. Toplu taramalarda mutlaka -SkipUpdateCheck kullanın:

Invoke-SpfDkimDmarc -File C:\Temp\domains.txt -SkipUpdateCheck

Split DNS ortamlarında -Server parametresi kritik. İç DNS sunucunuz dış dünyaya farklı cevap veriyor olabilir; denetimi mutlaka harici bir resolver üzerinden yapın:

Invoke-SpfDkimDmarc -File C:\Temp\domains.txt -Server 1.1.1.1 -SkipUpdateCheck

Çıktıyı CSV’ye aktarmak için:

Invoke-SpfDkimDmarc -File C:\Temp\domains.txt -SkipUpdateCheck |
    Export-Csv -Path C:\Temp\DomainHealthReport.csv -NoTypeInformation -Encoding UTF8

Modülü Güncel Tutmak

Update-ModuleDomainHealthChecker -Verbose

-Verbose parametresi mevcut sürümü de ekrana yazdırdığı için güncelleme öncesi hızlı kontrol imkanı sağlıyor.

Örnek Çıktı

Aşağıda Invoke-SpfDkimDmarc cmdlet’inin tek bir domain için ürettiği çıktının yapısını görebilirsiniz. Modül her kayıt için hem ham değeri hem de ...Advisory alanında ne yapılması gerektiğini söyleyen bir öneri döndürüyor:

Invoke-SpfDkimDmarc -Name bakicubuk.com | Format-List

Sadece durum özetine bakmak isterseniz Advisory alanlarını filtreleyebilirsiniz:

Invoke-SpfDkimDmarc -Name bakicubuk.com |
    Select-Object Name, SpfAdvisory, DmarcAdvisory, DkimAdvisory, DnsSecAdvisory |
    Format-List

Birden fazla domaini karşılaştırmalı görmek için tablo görünümü daha kullanışlı:

Invoke-SpfDkimDmarc -Name bakicubuk.com, microsoft.com -SkipUpdateCheck |
    Format-Table Name, SPFRecordDnsLookupCount, DmarcRecord, DnsSec -AutoSize -Wrap

Toplu Tarama ve Raporlama Script’i

Modülün ham çıktısı zengin ama okunması zor. Kurumsal envanterde asıl ihtiyaç duyulan şey, “hangi domainde ne eksik” sorusuna bakışta cevap veren bir tablo. Bunun için bir wrapper script yazdım:

.\Invoke-DomainHealthReport.ps1 -File .\domains.txt -OutputPath C:\Reports -Server 1.1.1.1

Script’in yaptıkları:

  • Domain listesini okuyup her biri için tam denetim çalıştırır
  • Her kayıt için OK / EKSİK / UYARI şeklinde bir durum üretir
  • 100 üzerinden bir sağlık skoru hesaplar (DMARC ve SPF’e daha yüksek ağırlık verir)
  • Renk kodlu, filtrelenebilir bir HTML rapor üretir
  • Aynı verinin ham halini CSV olarak kaydeder
  • Konsola özet tablo basar

Script tam haliyle GitHub hesabımda: https://github.com/bakicubuk/Domain_SpfDkimDmarc_Checker

Script’i Yazarken Karşılaştığım Tuzaklar

Modülün çıktısını programatik olarak işleyecekseniz iki noktaya dikkat edin, ikisi de sessizce yanlış sonuç üretir:

1. Kayıt bulunamadığında alan boş kalmıyor. Modül BimiRecord alanına We couldn't find a BIMI record associated with your domain. gibi bir açıklama metni yazıyor. Aynısı DKIM, MTA-STS, TLS-RPT ve CAA için de geçerli. IsNullOrWhiteSpace ile kontrol ederseniz kaydı olmayan her domain “kaydı var” olarak işaretlenir. Doğrusu, geçerli kayıt prefix’ini aramaktır: v=STSv1v=TLSRPTv1v=BIMI1 gibi.

2. SPFRecordDnsLookupCount bir sayı değil. 7/10 (OK) formatında metin döner. Sayısal karşılaştırma yapmadan önce regex ile ayrıştırmanız gerekir. Üstelik metindeki “Ok” ifadesine güvenemezsiniz: lookup sayısı 11 olduğunda bile çıktı 11/10 (Ok, but watch your DNS Lookups!) şeklinde gelir. Script’imde hem metni hem ayrıştırılmış sayıyı birlikte kontrol etmemin nedeni bu.

Pratikte Nasıl Konumlandırmalı?

Genel görünüm: bu modülü tek seferlik bir kontrol aracı değil, periyodik bir denetim adımı olarak düşünmek gerekir. Önerdiğim yaklaşım:

  1. Kurumun sahip olduğu tüm domainleri tek bir listede toplayın. Kullanılmayan ve park edilmiş olanlar dahil.
  2. Aylık zamanlanmış görev ile taramayı çalıştırıp CSV çıktısını tarihli olarak arşivleyin.
  3. İki rapor arasındaki farkı karşılaştırın. Beklenmedik bir SPF değişikliği ya da kaybolan bir DKIM kaydı, erken uyarı sinyalidir.
  4. Park edilmiş ve hiç e-posta göndermeyen domainlere mutlaka v=spf1 -all ve v=DMARC1 ; p=reject; kaydı ekleyin. Saldırganların en çok kullandığı hedef, bu unutulmuş domainlerdir.
  5. SPF lookup sayısı 8’i geçen domainleri işaretleyin. Henüz limit aşılmamış olsa bile, yeni bir servis eklendiğinde kayıt sessizce geçersiz hale gelecektir.

Kaynaklar

Sonuç

E-posta kimlik doğrulama kayıtları, bir kez kurulduktan sonra unutulmaya en müsait yapılandırmalardan biri. DomainHealthChecker bu kayıtları tek komutla görünür kılıyor, üstelik sadece kaydı göstermekle kalmayıp SPF lookup sayısı gibi standart ihlallerini de yakalıyor. Envanterinizde birden fazla domain varsa, bu taramayı rutin güvenlik kontrol listenize eklemenizi öneririm.

Depomdaki Farklar

Bu yazıda kullandığım depo, orijinal modülün bir kopyasıdır ve tek bir düzeltme içerir: public\Get-BimiRecord.ps1 dosyasının başındaki hatalı modül yükleyici blok kaldırılmıştır. Bu düzeltme olmadan Get-BimiRecord fonksiyonu yüklenmiyor ve modül 10 yerine 9 komut sunuyor. Değişikliği orijinal projeye de bildirdim.

Lisans ve Katkı Notu

DomainHealthChecker modülü Martien van Dijk’e ait olup kendi lisansı altında dağıtılmaktadır. Depomda modülü klonlarken orijinal LICENSE dosyası korunmuştur. Bu yazıda paylaştığım Invoke-DomainHealthReport.ps1 script’i modülün kendisi değil, onun çıktısını raporlayan bağımsız bir wrapper’dır. Modülün güncel sürümünü her zaman orijinal kaynaktan takip etmenizi öneririm: https://github.com/T13nn3s/Invoke-SpfDkimDmarc/

Modülü geliştiren Martien van Dijk’e teşekkürler.

Başka bir yazımızda görüşmek dileğiyle…

Bir yanıt yazın

Başa Dön