Unbound DNS Sunucusunda Access Control ile Erişim Kısıtlama

DNS altyapısını kurarken en çok ihmal edilen konulardan biri erişim kontrolü. Unbound’u kuruyorsun, çalışıyor, sorgular yanıtlanıyor, her şey güzel görünüyor. Ama o Unbound hangi IP’den gelen sorguya yanıt veriyor? Herkese mi açık? Sadece kendi subnet’ine mi? Belirli VLAN’lara mı? Bu soruları sormadan geçen her gün, potansiyel bir open resolver problemi yaşama riskiyle geçiyor demektir. Bu yazıda Unbound’un access-control direktifini derinlemesine inceleyeceğiz, gerçek senaryolar üzerinden nasıl yapılandırıldığını göreceğiz.

Access Control Neden Bu Kadar Önemli?

Unbound varsayılan kurulumda oldukça muhafazakar davranır, yani localhost dışından gelen sorguları reddeder. Ama bu varsayılan davranış her zaman geçerli değil ve her dağıtımda aynı şekilde gelmiyor. CentOS/RHEL tabanlı sistemlerde bazen 0.0.0.0/0 allow gibi açık bir konfigürasyonla geldiğine şahit oldum. Bu da Unbound’unuzu internete açık bir open resolver haline getirebilir.

Open resolver ne demek? Herkesin DNS sorgusu gönderip yanıt alabileceği bir sunucu. DNS amplification saldırıları bu açıklığı kullanır. Küçük bir sorguya karşılık çok büyük yanıtlar döndürülür ve bu yanıtlar hedef sistemlere yönlendirilir. Sunucunuzun bant genişliği başkasının saldırısı için kullanılmış olur, hem siz zarar görürsünüz hem de siz saldırının parçası haline gelirsiniz.

Bunun dışında kurumsal ortamlarda erişim kontrolü güvenlik politikasının doğal bir parçası. Hangi sistemlerin hangi DNS sunucusuna sorgu gönderebileceğini belirlemek, ağ segmentasyonunun DNS katmanındaki yansıması.

Temel access-control Direktifi

Unbound konfigürasyonunda access-control direktifi server: bloğu içinde tanımlanır. Genel sözdizimi şu şekilde:

access-control: <ip-range> <action>

Burada CIDR notasyonuyla yazılır, ise şu değerlerden birini alır:

  • deny: Sorguyu sessizce düşürür, istemciye hiçbir şey dönmez
  • refuse: Sorguyu reddeder ve REFUSED hata kodu döner
  • allow: Sorguya izin verir, normal yanıt döner
  • allow_setrd: Sorguya izin verir ve RD (Recursion Desired) bitini zorla set eder
  • allow_snoop: Sorguya izin verir ve ayrıca CHAOS sınıfı sorgulara da yanıt verir (istatistik sorguları için kullanılır)
  • deny_non_local: Sadece yerel zone’lardaki kayıtlara yanıt verir, recursion yapmaz
  • refuse_non_local: deny_non_local gibi çalışır ama REFUSED döner

Kurallar sırayla değil, en spesifik eşleşme prensibine göre çalışır. Yani /32 bir kural /24‘e göre her zaman önceliklidir, konfigürasyondaki sırasına bakılmaksızın.

Basit Bir Konfigürasyon Örneği

Diyelim ki tek bir ofis ağınız var, 192.168.10.0/24 subnet’i kullanıyor. Sadece bu subnet’in DNS sorgularına yanıt vermek istiyorsunuz:

server:
    interface: 0.0.0.0
    port: 53
    
    # Önce herkesi reddet
    access-control: 0.0.0.0/0 refuse
    access-control: ::/0 refuse
    
    # Localhost'a izin ver
    access-control: 127.0.0.0/8 allow
    access-control: ::1 allow
    
    # Ofis ağına izin ver
    access-control: 192.168.10.0/24 allow

IPv6’yı da eklemeyi unutmayın. Sadece IPv4 kısıtlaması koysanız bile, sistem IPv6 üzerinden sorgu kabul edebilir. Her iki protokol için de kuralları tanımlamak iyi bir alışkanlık.

Çok Katmanlı Ağlarda Erişim Kontrolü

Gerçek kurumsal ortamlarda işler daha karmaşık. Birden fazla VLAN, farklı departmanlar, DMZ bölgesi, VPN subnet’leri… Her birinin DNS’e erişimi farklı koşullarda olabilir.

Şöyle bir senaryo düşünelim: Bir şirketin ağında üretim sunucuları (10.0.1.0/24), geliştirici makineleri (10.0.2.0/24), misafir WiFi (172.16.5.0/24) ve DMZ (10.0.100.0/28) var. DNS sunucumuz 10.0.0.10 adresinde çalışıyor.

server:
    interface: 0.0.0.0@53
    
    # Varsayılan: herkesi reddet
    access-control: 0.0.0.0/0 refuse
    access-control: ::/0 refuse
    
    # Localhost
    access-control: 127.0.0.0/8 allow
    access-control: ::1/128 allow
    
    # Üretim sunucuları - tam erişim
    access-control: 10.0.1.0/24 allow
    
    # Geliştirici makineleri - tam erişim
    access-control: 10.0.2.0/24 allow
    
    # DMZ - sadece yerel zone'lara erişim, dış recursion yok
    access-control: 10.0.100.0/28 deny_non_local
    
    # Misafir WiFi - tamamen reddet
    access-control: 172.16.5.0/24 refuse

DMZ için deny_non_local kullanmak ilginç bir seçim. Bu sayede DMZ’deki sunucular kendi iç zone kayıtlarına (dahili servisler, API endpoint’leri) ulaşabilirken, internete çıkış için recursion yapamaz. Güvenlik perspektifinden bakıldığında DMZ’deki bir sistem ele geçirilse bile DNS üzerinden dış dünyayla veri sızdırması zorlaşır.

VPN Kullanıcıları İçin Özel Kurallar

Uzaktan çalışma yaygınlaştıkça VPN subnet’leri de DNS konfigürasyonunun ayrılmaz parçası haline geldi. Tipik bir OpenVPN veya WireGuard kurulumunda VPN istemcileri farklı bir subnet üzerinden bağlanır:

server:
    interface: 0.0.0.0@53
    
    # Temel reddiye
    access-control: 0.0.0.0/0 refuse
    
    # İç ağlar
    access-control: 10.10.0.0/16 allow
    
    # VPN istemcileri - OpenVPN
    access-control: 10.20.0.0/24 allow
    
    # WireGuard peers
    access-control: 10.30.0.0/24 allow
    
    # Belirli bir VPN kullanıcısını kısıtla (örneğin test kullanıcısı)
    access-control: 10.20.0.50/32 deny_non_local
    
    # Localhost
    access-control: 127.0.0.0/8 allow
    access-control: ::1/128 allow

Burada 10.20.0.50/32 için özel kural dikkat çekici. Bu IP’ye atanmış VPN kullanıcısı iç kayıtlara ulaşabilir ama dış recursion yapamaz. Test ortamındaki bir kullanıcıyı veya kısıtlı erişimli bir contractor’ı bu şekilde izole edebilirsiniz.

access-control-tag ile Etiket Tabanlı Kontrol

Unbound’un daha az bilinen ama güçlü özelliklerinden biri etiket sistemi. define-tag, access-control-tag ve access-control-tag-action direktifleriyle istemci grupları oluşturabilir ve bu gruplar için farklı davranışlar tanımlayabilirsiniz:

server:
    interface: 0.0.0.0@53
    
    # Etiketleri tanımla
    define-tag: "internal external restricted"
    
    # Subnet'lere etiket ata
    access-control-tag: 10.0.0.0/8 "internal"
    access-control-tag: 192.168.0.0/16 "internal"
    access-control-tag: 172.16.100.0/24 "restricted"
    
    # Etiket bazında eylem
    access-control-tag-action: "internal" allow
    access-control-tag-action: "restricted" deny_non_local
    
    # Varsayılan
    access-control: 0.0.0.0/0 refuse
    access-control: 10.0.0.0/8 allow
    access-control: 192.168.0.0/16 allow
    access-control: 172.16.100.0/24 deny_non_local

Etiket sistemi özellikle RPZ (Response Policy Zone) ile birlikte kullanıldığında çok daha güçlü hale geliyor. Belirli etiketli istemciler için farklı RPZ politikaları uygulayabiliyorsunuz, ama bu konu başlı başına ayrı bir yazı.

Konfigürasyonu Test Etme ve Doğrulama

Konfigürasyonu yazdınız, şimdi test etme zamanı. Önce syntax kontrolü:

unbound-checkconf /etc/unbound/unbound.conf

Hata yoksa servisi yeniden yükleyin (restart değil, reload tercih edin, böylece downtime olmaz):

systemctl reload unbound

Artık erişim kontrolünün çalışıp çalışmadığını test edelim. İzin verilen bir IP’den sorgu testi için dig kullanabiliriz. Ama önce, izin verilmeyen bir IP’den simüle etmek gerekirse, nmap‘in --source-ip veya dig‘in -b parametresiyle kaynak IP belirleyebilirsiniz (aynı subnet’teyseniz):

# İzin verilen bir arayüzden test
dig @10.0.0.10 google.com A

# REFUSED yanıtı beklenen test (izin verilmeyen kaynak)
dig @10.0.0.10 google.com A

# Unbound istatistiklerini kontrol et
unbound-control stats_noreset | grep -E "num.queries|num.cachehits|unwanted"

REFUSED yanıtı alıyorsanız ve kaynağınız kısıtlı subnet’teyse kural çalışıyor demektir. Log’lara bakmak için:

# Unbound logları (sistemden sisteme değişir)
journalctl -u unbound -f

# Verbosity artırarak daha detaylı log
# unbound.conf içinde:
# verbosity: 2
# logfile: "/var/log/unbound/unbound.log"

Gerçek Dünya Sorunu: Monitoring Sistemleri

Bir monitoring sisteminin (Zabbix, Nagios, Prometheus) DNS sunucunuzu kontrol etmesi gerekebilir. Bu sistemler genellikle CHAOS sınıfı sorgularla Unbound’un durumunu kontrol eder. Normal allow bu sorgulara yanıt vermez, allow_snoop gereklidir:

server:
    # Monitoring sunucusu için özel kural
    access-control: 10.0.50.5/32 allow_snoop
    
    # Diğer kurallar
    access-control: 0.0.0.0/0 refuse
    access-control: 10.0.0.0/8 allow

Monitoring sunucusunun IP’sini allow_snoop ile tanımladıktan sonra şu sorguları çalıştırabilirsiniz:

# Unbound versiyonunu sorgula (monitoring için kullanışlı)
dig @10.0.0.10 id.server CHAOS TXT
dig @10.0.0.10 version.server CHAOS TXT

# Cache istatistikleri
unbound-control -s 10.0.0.10 stats

Ama dikkatli olun, allow_snoop verdiğiniz IP aynı zamanda cache’i dump edebilir, flush yapabilir gibi bazı ek yetenekler kazanır. Bunu sadece gerçekten güvendiğiniz monitoring sistemlerine verin.

include Direktifi ile Modüler Konfigürasyon

Büyük ortamlarda tüm access-control kurallarını tek dosyaya yazmak yönetimi zorlaştırır. Unbound’un include direktifini kullanarak modüler yapı kurabilirsiniz:

# /etc/unbound/unbound.conf
server:
    interface: 0.0.0.0@53
    
    # Temel ayarlar burada
    verbosity: 1
    
include: "/etc/unbound/access-control.conf"
include: "/etc/unbound/local-zones.conf"
# /etc/unbound/access-control.conf
    # Varsayılan reddetme
    access-control: 0.0.0.0/0 refuse
    access-control: ::/0 refuse
    
    # Localhost
    access-control: 127.0.0.0/8 allow
    access-control: ::1/128 allow
    
    # Kurumsal ağlar
    access-control: 10.0.0.0/8 allow
    access-control: 192.168.0.0/16 allow
    
    # DMZ
    access-control: 10.0.100.0/24 deny_non_local
    
    # VPN
    access-control: 172.16.200.0/24 allow

Bu yapıyla erişim kurallarını ayrı bir dosyada yönetir, Git ile versiyon kontrolüne alır, değişiklikleri daha kolay takip edebilirsiniz.

Unbound-control ile Dinamik Kural Yönetimi

Servis yeniden başlatmadan bazı işlemler yapılabilir, ama access-control kuralları runtime’da dinamik olarak değiştirilemez; konfigürasyon dosyasını düzenlemeniz ve reload etmeniz gerekir. Ancak unbound-control ile cache flush, istatistik görüntüleme ve zone yönetimi yapılabilir:

# Unbound-control'ü etkinleştir
# unbound.conf içinde:
# remote-control:
#     control-enable: yes
#     control-interface: 127.0.0.1
#     control-port: 8953

# Sertifikaları oluştur
unbound-control-setup

# Bağlantıyı test et
unbound-control status

# Belirli bir IP'nin cache'ini temizle (dolaylı yönetim)
unbound-control flush_zone example.com

# Aktif konfigürasyonu görüntüle
unbound-control dump_infra

Otomasyona entegrasyon için şöyle bir script yazabilirsiniz, bir IP’nin engelli olup olmadığını test eder:

#!/bin/bash
# dns-access-test.sh
# Kullanım: ./dns-access-test.sh <test-ip> <dns-server>

TEST_IP=$1
DNS_SERVER=$2
TEST_DOMAIN="google.com"

if [ -z "$TEST_IP" ] || [ -z "$DNS_SERVER" ]; then
    echo "Kullanim: $0 <test-ip> <dns-sunucu>"
    exit 1
fi

# dig ile test (kaynak IP belirterek)
RESULT=$(dig -b ${TEST_IP} @${DNS_SERVER} ${TEST_DOMAIN} A +short 2>&1)
STATUS=$(dig -b ${TEST_IP} @${DNS_SERVER} ${TEST_DOMAIN} A +noall +comments 2>&1 | grep -i "status:" | awk '{print $4}')

echo "Test IP: $TEST_IP"
echo "DNS Sunucu: $DNS_SERVER"
echo "Sorgu durumu: $STATUS"

if [ "$STATUS" == "NOERROR" ]; then
    echo "SONUC: Erisime izin verildi"
elif [ "$STATUS" == "REFUSED" ]; then
    echo "SONUC: Erisim reddedildi (REFUSED)"
else
    echo "SONUC: Bilinmeyen durum - $STATUS"
fi

Yaygın Hatalar ve Çözümleri

Yıllar içinde gördüğüm bazı yaygın hataları paylaşayım:

IPv6’yı unutmak: Sadece IPv4 kuralları tanımladığınızda, sistem IPv6 üzerinden gelen sorgulara hâlâ yanıt verebilir. Her zaman hem 0.0.0.0/0 hem ::/0 için kural yazın.

Sıra mantığını yanlış anlamak: Unbound’da access-control’ün çalışma mantığını Apache veya iptables’dan alışkanlıkla “ilk eşleşen kazanır” olarak düşünmek. Unbound “en spesifik eşleşme kazanır” mantığıyla çalışır.

interface ve access-control tutarsızlığı: interface: 127.0.0.1 yazıp access-control: 10.0.0.0/8 allow eklemek bir şey değiştirmez. Unbound zaten sadece localhost’u dinliyorsa, o interface’e uzak IP’lerden zaten ulaşamazsınız. interface ayarı hangi adreste dinleneceğini, access-control ise gelen sorgulara nasıl davranılacağını belirler.

reload yerine restart: Konfigürasyon değişikliklerinde systemctl restart unbound yerine systemctl reload unbound veya unbound-control reload kullanın. Cache korunur, servis kesintisi olmaz.

Test etmeden prod’a almak: Konfigürasyon değişikliklerini önce bir test ortamında deneyin. unbound-checkconf syntax hatalarını yakalar ama mantıksal hataları yakalamaz.

Güvenlik Duvarı ile Katmanlı Savunma

Access-control tek başına yeterli değil, güvenlik duvarı kurallarıyla desteklenmelidir. Linux’ta nftables veya iptables ile DNS portunu kısıtlamak, Unbound’un access-control’ünden önce bir filtre katmanı oluşturur:

# nftables ile DNS erişim kısıtlaması
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 ; }

# Sadece belirli subnet'lerden UDP/TCP 53'e izin ver
nft add rule inet filter input ip saddr 10.0.0.0/8 udp dport 53 accept
nft add rule inet filter input ip saddr 10.0.0.0/8 tcp dport 53 accept
nft add rule inet filter input ip saddr 192.168.0.0/16 udp dport 53 accept
nft add rule inet filter input ip saddr 192.168.0.0/16 tcp dport 53 accept

# Diğer DNS trafiğini düşür
nft add rule inet filter input udp dport 53 drop
nft add rule inet filter input tcp dport 53 drop

Bu yaklaşım “defense in depth” prensibini uygular. Bir katman atlanırsa diğeri devreye girer. Unbound’un access-control mekanizması uygulama katmanında çalışırken, güvenlik duvarı network katmanında çalışır.

Sonuç

Unbound’da access-control yapılandırması basit görünse de doğru uygulanması dikkat gerektiriyor. Temel prensipler şu şekilde özetlenebilir: önce herkesi reddet, sonra ihtiyacın kadar izin aç. DMZ ve kısıtlı bölgeler için deny_non_local veya refuse_non_local kullan. Monitoring sistemleri için allow_snoop‘u sadece gerçekten gerekli IP’lere ver. IPv4 ve IPv6’yı her zaman birlikte düşün. Konfigürasyon değişikliklerini mutlaka test ortamında dene ve unbound-checkconf ile doğrula. Unbound’un access-control’ünü güvenlik duvarı kurallarıyla destekle.

DNS erişim kontrolü, ağ güvenliğinin görünmez ama kritik bir katmanı. Kurulumu birkaç satır konfigürasyondan ibaret olsa da, arkasındaki düşünce süreci ağ topolojinizi, güvenlik gereksinimlerinizi ve operasyonel ihtiyaçlarınızı kapsamlı biçimde anlamayı gerektiriyor. Bu yapıyı bir kez doğru kurduğunuzda, DNS altyapınız hem güvenli hem de yönetilebilir hale geliyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir