Split-Horizon DNS Yapılandırması: İç ve Dış Ağlar İçin Farklı DNS Yanıtları

Bir şirkette hem iç ağ hem de dış ağ kullanıcılarına hizmet veriyorsanız, DNS yapılandırması düşündüğünüzden çok daha kritik bir hal alıyor. Klasik yaklaşımla herkes aynı DNS yanıtını alır, ama bu çoğu zaman sorun çıkarır. İç kullanıcılar dışarıya çıkıp tekrar içeri giren trafikle boğuşur, güvenlik açıkları oluşur ve performans berbat olur. İşte tam bu noktada Split-Horizon DNS devreye giriyor.

Split-Horizon DNS Nedir?

Split-Horizon DNS, aynı alan adı için farklı istemci gruplarına farklı DNS yanıtları döndürme tekniğidir. Temel mantık şu: iç ağdaki bir kullanıcı app.sirket.com adresini sorguladığında 192.168.1.50 gibi bir iç IP alırken, dış ağdan biri aynı sorguyu yaptığında 203.0.113.10 gibi bir dış IP alır.

Bu yapıya bazı kaynaklarda Split-Brain DNS veya Split-View DNS de denir. Farklı isimler, aynı konsept.

Neden buna ihtiyaç duyarsınız?

  • Hairpin NAT sorunlarını çözmek: İç kullanıcılar dış IP’ye gidip geri dönmek yerine direkt iç IP’ye bağlanır
  • İç kaynakları gizlemek: Dış dünya iç sunucularınızın IP adreslerini görmez
  • Performans: İç trafik NAT üzerinden geçmez, gecikme azalır
  • Güvenlik: Hassas iç servislerin DNS kayıtları dışarıdan sorgulanamaz

Temel Mimari

Yapıyı kafanızda oturtmak için şöyle düşünün: DNS sunucunuz istemcinin nereden geldiğine bakarak farklı “zone view” kullanır. BIND terminolojisinde buna view deniyor. PowerDNS ve Unbound’da da benzer mekanizmalar mevcut.

Tipik bir senaryo:

  • İç görünüm (internal view): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 bloklarından gelen sorgulara iç IP’lerle cevap verir
  • Dış görünüm (external view): Geri kalan her şeye dış IP’lerle cevap verir

BIND9 ile Split-Horizon Kurulumu

BIND9, bu iş için en yaygın kullanılan sunucu. Ubuntu/Debian üzerinde kurulum:

apt update && apt install -y bind9 bind9utils bind9-doc
systemctl enable --now named

named.conf Yapılandırması

Ana yapılandırma dosyasını düzenleyelim:

# /etc/bind/named.conf.options

acl "internal_networks" {
    10.0.0.0/8;
    172.16.0.0/12;
    192.168.0.0/16;
    127.0.0.1;
    ::1;
};

options {
    directory "/var/cache/bind";
    
    recursion yes;
    allow-recursion { internal_networks; };
    
    dnssec-validation auto;
    
    listen-on { any; };
    listen-on-v6 { any; };
    
    // Dış sorgular için recursion kapalı
    allow-query { any; };
    allow-transfer { none; };
};

View Tanımlamaları

Asıl sihir burada gerçekleşiyor. named.conf.local dosyasına view’ları ekliyoruz:

# /etc/bind/named.conf.local

view "internal" {
    match-clients { internal_networks; };
    recursion yes;
    
    // İç zone tanımı
    zone "sirket.com" {
        type master;
        file "/etc/bind/zones/internal/db.sirket.com";
        allow-query { internal_networks; };
    };
    
    // Reverse zone
    zone "168.192.in-addr.arpa" {
        type master;
        file "/etc/bind/zones/internal/db.192.168";
        allow-query { internal_networks; };
    };
    
    // Root hints için
    include "/etc/bind/named.conf.default-zones";
};

view "external" {
    match-clients { any; };
    recursion no;
    
    zone "sirket.com" {
        type master;
        file "/etc/bind/zones/external/db.sirket.com";
        allow-query { any; };
    };
};

Önemli not: match-clients direktifi sırayla işlenir. İlk eşleşen view kullanılır. Bu yüzden internal view’ı her zaman önce tanımlayın.

İç Zone Dosyası

# /etc/bind/zones/internal/db.sirket.com

$TTL 300
@   IN  SOA ns1.sirket.com. admin.sirket.com. (
            2024010101  ; Serial
            3600        ; Refresh
            900         ; Retry
            604800      ; Expire
            300 )       ; Minimum TTL

; Name servers
@       IN  NS  ns1.sirket.com.
ns1     IN  A   192.168.1.10

; İç servisler - gerçek iç IP'ler
@       IN  A   192.168.1.100
www     IN  A   192.168.1.100
app     IN  A   192.168.1.50
mail    IN  A   192.168.1.25
intranet IN A   192.168.1.30
db      IN  A   192.168.1.75
vpn     IN  A   192.168.1.1

; Sadece iç ağdan erişilebilen servisler
monitoring  IN  A   192.168.1.200
jenkins     IN  A   192.168.1.150
gitlab      IN  A   192.168.1.160

Dış Zone Dosyası

# /etc/bind/zones/external/db.sirket.com

$TTL 3600
@   IN  SOA ns1.sirket.com. admin.sirket.com. (
            2024010101  ; Serial
            86400       ; Refresh
            7200        ; Retry
            604800      ; Expire
            3600 )      ; Minimum TTL

; Name servers
@       IN  NS  ns1.sirket.com.
@       IN  NS  ns2.sirket.com.
ns1     IN  A   203.0.113.10
ns2     IN  A   203.0.113.11

; Dışarıya açık servisler - dış IP'ler
@       IN  A   203.0.113.20
www     IN  A   203.0.113.20
app     IN  A   203.0.113.21
mail    IN  A   203.0.113.22
vpn     IN  A   203.0.113.1

; MX kayıtları
@       IN  MX  10  mail.sirket.com.

; SPF ve diğer TXT kayıtları
@       IN  TXT "v=spf1 ip4:203.0.113.0/24 ~all"

; Dikkat: monitoring, jenkins, gitlab burada YOK
; Bu kayıtlar dış dünyaya görünmez

Yapılandırmayı Test Etme

# Syntax kontrolü
named-checkconf /etc/bind/named.conf

# Zone dosyası kontrolü - internal
named-checkzone sirket.com /etc/bind/zones/internal/db.sirket.com

# Zone dosyası kontrolü - external
named-checkzone sirket.com /etc/bind/zones/external/db.sirket.com

# BIND'ı yeniden başlat
systemctl restart named

# İç ağdan test (192.168.1.x üzerinden)
dig @192.168.1.10 app.sirket.com

# Beklenen çıktı: 192.168.1.50

# Dış ağı simüle etmek için farklı source IP (veya dış bir makineden)
dig @203.0.113.10 app.sirket.com
# Beklenen çıktı: 203.0.113.21

Windows DNS Server ile Split-Horizon

BIND her yerde çalışsa da Windows ortamında Active Directory ile entegre DNS kullanıyorsanız durum biraz farklı. Windows DNS Server, view konseptini BIND gibi desteklemez. Bunun yerine birden fazla DNS sunucusu kullanarak ayrıştırma yapılır.

Önerilen mimari: İki ayrı DNS sunucusu

  • İç DNS: Active Directory integrated zone, sadece iç ağdan erişilebilir
  • Dış DNS: Sadece dış kayıtları olan, DMZ veya cloud’da çalışan bir sunucu

Firewall kuralları ile bunu zorlayabilirsiniz: iç istemciler sadece iç DNS’e, dış istemciler sadece dış DNS’e ulaşabilsin.

Gerçek Dünya Senaryosu: E-ticaret Şirketi

Diyelim ki orta ölçekli bir e-ticaret şirketinin sysadminliğini yapıyorsunuz. Mimari şöyle:

  • Web sunucusu: İç IP 192.168.10.50, dış NAT IP 185.100.200.50
  • API sunucusu: İç IP 192.168.10.60, dış NAT IP 185.100.200.60
  • Admin paneli: İç IP 192.168.10.70, sadece iç ağdan erişilebilir
  • Ödeme servisi: İç IP 192.168.10.80, dış NAT IP 185.100.200.80

Bu senaryoda split-horizon olmadan ne olur? Bir iç kullanıcı www.magazam.com yazdığında router dış IP’ye istek gönderir, NAT bunu içeri çevirir, cevap tekrar dışarı çıkar ve içeri döner. Bu “hairpin NAT” veya “NAT loopback” meselesi ciddi gecikme ve bazı router modellerinde doğrudan bağlantı hatası oluşturabilir.

Split-horizon ile iç kullanıcı direkt 192.168.10.50’ye gider, trafik ağ içinde kalır.

# Senaryo için BIND view konfigürasyonu
view "ecommerce_internal" {
    match-clients { 192.168.0.0/16; 10.0.0.0/8; };
    recursion yes;
    
    zone "magazam.com" {
        type master;
        file "/etc/bind/zones/internal/db.magazam.com";
    };
    
    include "/etc/bind/named.conf.default-zones";
};

view "ecommerce_external" {
    match-clients { any; };
    recursion no;
    
    zone "magazam.com" {
        type master;
        file "/etc/bind/zones/external/db.magazam.com";
    };
};

Unbound ile Split-Horizon

Bazı ortamlarda Unbound tercih edilir. Unbound’da bu iş biraz farklı çalışır, local-zone ve local-data direktifleri kullanılır:

# /etc/unbound/unbound.conf

server:
    interface: 0.0.0.0
    access-control: 192.168.0.0/16 allow
    access-control: 10.0.0.0/8 allow
    access-control: 0.0.0.0/0 refuse
    
    # İç zone tanımları
    local-zone: "sirket.com." static
    local-data: "www.sirket.com. IN A 192.168.1.100"
    local-data: "app.sirket.com. IN A 192.168.1.50"
    local-data: "mail.sirket.com. IN A 192.168.1.25"
    local-data: "monitoring.sirket.com. IN A 192.168.1.200"
    
    # PTR kayıtları
    local-data-ptr: "192.168.1.100 www.sirket.com"
    local-data-ptr: "192.168.1.50 app.sirket.com"

Unbound genellikle recursive resolver olarak kullanılır ve iç ağ için mükemmeldir. Dış DNS için ayrı bir authoritative sunucu (Bind, PowerDNS veya bulut sağlayıcısı) kullanırsınız.

Sık Yapılan Hatalar ve Çözümleri

Hata 1: Serial Numarası Senkronizasyonu

İç ve dış zone dosyalarının serial numaralarını senkronize tutmak zorunda değilsiniz, ama unutmak ciddi sorunlara yol açar. Bir script ile bunu otomatize edin:

#!/bin/bash
# /usr/local/bin/update-dns-serial.sh

DATE=$(date +%Y%m%d)
COUNTER=01

# Internal zone
CURRENT_SERIAL=$(grep -oP 'd{10}' /etc/bind/zones/internal/db.sirket.com | head -1)
NEW_SERIAL="${DATE}${COUNTER}"

if [ "$CURRENT_SERIAL" -lt "$NEW_SERIAL" ]; then
    sed -i "s/$CURRENT_SERIAL/$NEW_SERIAL/g" /etc/bind/zones/internal/db.sirket.com
    sed -i "s/$CURRENT_SERIAL/$NEW_SERIAL/g" /etc/bind/zones/external/db.sirket.com
    rndc reload
    echo "Serial güncellendi: $NEW_SERIAL"
else
    echo "Serial zaten güncel"
fi

Hata 2: include Direktiflerinde View Çakışması

Her view içinde include "/etc/bind/named.conf.default-zones"; kullanıyorsanız, bu root zone ve localhost zone’larını içerir. Birden fazla view’da aynı include kullanmak çakışmaya neden olur. Çözüm:

# Her view'da root hints ayrı tanımlanmalı
view "internal" {
    match-clients { internal_networks; };
    
    zone "." {
        type hint;
        file "/usr/share/dns/root.hints";
    };
    
    zone "localhost" {
        type master;
        file "/etc/bind/db.local";
    };
    
    zone "sirket.com" {
        type master;
        file "/etc/bind/zones/internal/db.sirket.com";
    };
};

Hata 3: Önbellek Zehirlenmesi Riski

Dış view’da recursion açık bırakmak DNS amplification saldırılarına davetiye çıkarır. Her zaman:

view "external" {
    match-clients { any; };
    recursion no;           # Kesinlikle kapalı olmalı
    additional-from-auth no;
    additional-from-cache no;
    
    zone "sirket.com" {
        type master;
        file "/etc/bind/zones/external/db.sirket.com";
    };
};

Monitoring ve Doğrulama

Yapılandırmanın doğru çalıştığını sürekli kontrol etmek gerekir. Basit bir kontrol scripti:

#!/bin/bash
# /usr/local/bin/check-split-dns.sh

INTERNAL_DNS="192.168.1.10"
EXTERNAL_DNS="203.0.113.10"
TEST_DOMAIN="app.sirket.com"
EXPECTED_INTERNAL="192.168.1.50"
EXPECTED_EXTERNAL="203.0.113.21"

echo "=== Split-DNS Kontrol ==="

# İç DNS kontrolü
INTERNAL_RESULT=$(dig +short @$INTERNAL_DNS $TEST_DOMAIN 2>/dev/null)
if [ "$INTERNAL_RESULT" = "$EXPECTED_INTERNAL" ]; then
    echo "[OK] İç DNS doğru: $TEST_DOMAIN -> $INTERNAL_RESULT"
else
    echo "[HATA] İç DNS yanlış! Beklenen: $EXPECTED_INTERNAL, Alınan: $INTERNAL_RESULT"
    exit 1
fi

# Dış DNS kontrolü
EXTERNAL_RESULT=$(dig +short @$EXTERNAL_DNS $TEST_DOMAIN 2>/dev/null)
if [ "$EXTERNAL_RESULT" = "$EXPECTED_EXTERNAL" ]; then
    echo "[OK] Dış DNS doğru: $TEST_DOMAIN -> $EXTERNAL_RESULT"
else
    echo "[HATA] Dış DNS yanlış! Beklenen: $EXPECTED_EXTERNAL, Alınan: $EXTERNAL_RESULT"
    exit 1
fi

echo "=== Tüm kontroller başarılı ==="

Bu scripti cron’a ekleyin:

# /etc/cron.d/check-split-dns
*/5 * * * * root /usr/local/bin/check-split-dns.sh >> /var/log/dns-check.log 2>&1

DNSSEC ile Split-Horizon

DNSSEC kullanıyorsanız dikkatli olun. İç ve dış zone’lar ayrı imzalanmalıdır. İç zone’u imzalamanıza gerek olmayabilir, zira iç ağı zaten güvenli kabul ediyorsunuzdur. Ama dış zone mutlaka imzalanmalı:

# Dış zone için DNSSEC anahtarı oluşturma
dnssec-keygen -a RSASHA256 -b 2048 -n ZONE sirket.com
dnssec-keygen -f KSK -a RSASHA256 -b 4096 -n ZONE sirket.com

# Zone'u imzalama
dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) 
    -N INCREMENT -o sirket.com 
    -t /etc/bind/zones/external/db.sirket.com

Bulut Ortamları ile Entegrasyon

Hybrid cloud kullanıyorsanız, AWS Route 53 Private Hosted Zone veya Azure Private DNS gibi servisleri split-horizon için kullanabilirsiniz. AWS tarafında:

  • Public Hosted Zone: Dış kullanıcılar için gerçek public IP’ler
  • Private Hosted Zone: VPC içindeki kaynaklar için özel IP’ler

Bu yapıda Route 53 Resolver kuralları ile on-premise DNS’inizi bulutla entegre edebilirsiniz. Şirket içi DNS sunucunuz sirket.com için authoritative kalırken, bulut kaynakları için Route 53 devreye girer.

Sonuç

Split-Horizon DNS ilk bakışta karmaşık görünse de pratikte son derece mantıklı ve yönetilebilir bir yapı. Kurulumun en kritik noktaları şunlar: view sıralamasını doğru yapmak, dış view’da recursion’ı kapatmak, zone serial numaralarını düzenli güncellemek ve otomatik monitoring eklemek.

Bu yapıyı doğru kurduğunuzda hairpin NAT sorunları tarihe karışır, iç servisler dış dünyaya görünmez hale gelir ve ağ trafiğiniz optimize edilmiş olur. Özellikle 50 kişinin üzerinde çalışan ve kendi sunucu altyapısı bulunan her şirketin bu yapıyı uygulaması gerekir.

Yapılandırmayı geliştirme ortamında mutlaka test edin, zone dosyalarını versiyon kontrolüne (git) alın ve değişiklikleri otomatize edin. DNS sorunları genellikle gece yarısı ortaya çıkar, önceden sağlam bir yapı kurmak o geceleri kurtarır.

Bir yanıt yazın

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