DNSSEC and Split-Horizon DNS
DNSSEC - DNS Security Extensions
Section titled “DNSSEC - DNS Security Extensions”The Problem DNSSEC Solves
Section titled “The Problem DNSSEC Solves”Standard DNS has no authentication. An attacker who can intercept DNS responses can return forged answers - redirecting mybank.com to a phishing site. This is DNS spoofing (also called DNS cache poisoning).
DNSSEC adds cryptographic signatures to DNS records. A resolver can verify that the data came from the authoritative server and hasn’t been tampered with.
How DNSSEC Works: Chain of Trust
Section titled “How DNSSEC Works: Chain of Trust”DNSSEC builds a chain of trust from the root zone down to the authoritative zone:
Root Zone (.) │ Signed by Root KSK (Root KSK is self-signed and published in trust anchors) │ ▼TLD Zone (.com) │ DS record in root zone → validates .com DNSKEY │ ▼Domain Zone (example.com) │ DS record in .com zone → validates example.com DNSKEY │ ▼Resource Records (A, MX, AAAA, etc.) RRSIG record → signature over the resource recordsDNSSEC Record Types
Section titled “DNSSEC Record Types”| Record | Purpose |
|---|---|
| DNSKEY | Zone’s public key(s). Two types: ZSK (Zone Signing Key) and KSK (Key Signing Key) |
| RRSIG | Cryptographic signature over a set of resource records |
| DS (Delegation Signer) | Hash of child zone’s KSK, stored in parent zone. Creates the chain link |
| NSEC / NSEC3 | Proves a domain name does NOT exist (authenticated denial of existence) |
Validation in Practice
Section titled “Validation in Practice”Resolver wants example.com A record:
1. Gets A record + RRSIG from example.com nameserver2. Gets DNSKEY for example.com zone3. Verifies RRSIG matches A record, signed by ZSK in DNSKEY4. Gets DS record for example.com from .com zone5. Verifies DS is hash of example.com DNSKEY (KSK)6. Gets DNSKEY for .com zone7. Verifies .com DNSKEY matches DS signed by root zone8. Trusts root zone based on built-in trust anchorChecking DNSSEC
Section titled “Checking DNSSEC”# Check if a domain has DNSSEC enableddig example.com +dnssec
# Check DNSKEY recordsdig DNSKEY example.com
# Check DS record in parent zonedig DS example.com @a.gtld-servers.net
# Test full DNSSEC validationdig +sigchase example.com# (requires BIND's dig with +sigchase support)
# Or use the online DNSSEC debugger:# https://dnssec-debugger.verisignlabs.com/
# Check if a resolver validates DNSSECdig sigok.verteiltesysteme.net +dnssec# If you get SERVFAIL, resolver validates (this domain has a deliberately broken sig)# If you get an answer, resolver does NOT validateSplit-Horizon DNS (Split-Brain DNS)
Section titled “Split-Horizon DNS (Split-Brain DNS)”The Pattern
Section titled “The Pattern”Split-horizon DNS (also called split-brain or split-view DNS) means the same domain name returns different answers depending on who’s asking.
External DNS (Internet) query: api.example.com answer: 203.0.113.10 (public load balancer)
Internal DNS (Corporate network) query: api.example.com answer: 10.0.1.50 (directly to internal service)Why Use It?
Section titled “Why Use It?”| Use Case | Why |
|---|---|
| Direct internal access | Employees access internal app via internal IP, avoiding NAT hairpin |
| Hairpin NAT avoidance | Without split-horizon, internal traffic goes Internet→Firewall→back in (slow, expensive) |
| Security | Internal hosts see more services than external hosts (e.g., internal admin panels) |
| Dev/staging environments | app.example.com → production externally, staging server internally |
| Cloud migration | During cutover, internal sees new host, external still sees old |
Architecture
Section titled “Architecture” ┌─────────────────────┐ │ Internal DNS │Internet users ──X──│ (Active Directory │◀── Employees (can't reach) │ or BIND) │ (internal clients) └─────────────────────┘ api.example.com → 10.0.1.50
┌─────────────────────┐Internet users ────▶│ Public DNS │ (8.8.8.8, etc.) │ (Cloudflare, │ │ Route53, etc.) │ └─────────────────────┘ api.example.com → 203.0.113.10Implementing Split-Horizon with BIND (named)
Section titled “Implementing Split-Horizon with BIND (named)”# Define which networks are "internal"acl "internal" { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; localhost; localnets;};
# Two views: internal and externalview "internal" { match-clients { internal; }; # only respond to internal clients
zone "example.com" IN { type master; file "/etc/named/internal/example.com.zone"; # internal records };};
view "external" { match-clients { any; }; # all other clients
zone "example.com" IN { type master; file "/etc/named/external/example.com.zone"; # public records };};Implementing with systemd-resolved (Linux clients)
Section titled “Implementing with systemd-resolved (Linux clients)”# Point a specific domain to an internal DNS server# Useful for accessing internal corp domains from personal machine via VPN
# /etc/systemd/resolved.conf.d/internal.conf:[Resolve]DNS=10.0.0.53Domains=~corp.example.com # only resolve corp.example.com internally
# Applysudo systemctl restart systemd-resolved
# Verify routingresolvectl query app.corp.example.comGotchas and Pitfalls
Section titled “Gotchas and Pitfalls”| Pitfall | What happens | Fix |
|---|---|---|
| VPN clients use external DNS | Internal names don’t resolve through VPN | Configure VPN to push internal DNS and search domains |
| Cache poisoning of internal resolver | Attacker injects bad records into split-horizon resolver | DNSSEC on internal zone, TSIG for zone transfers |
| Dev forgets split-horizon exists | Tests with external DNS, fails in split-horizon env | Document clearly; use /dev/hosts or .override files for dev |
| Zone sync lag | Internal and external zones drift | Automate sync; use the same zone file with views filtering records |