Chapter: 5 (DNS)
Duration: 90 minutes
Tools: BIND 9 (named), dig, Unbound, dnssec-keygen
Points: 10
Objectives
- Author a DNSSEC-signed zone using BIND 9 inline signing
- Verify the chain of trust with dig +dnssec
- Deliberately break the DNSSEC chain and observe validation failure
- Configure Unbound as a validating resolver and verify it accepts/rejects the zone
- Configure DoT forwarding in Unbound
Setup
Ensure BIND 9 and Unbound are installed:
sudo apt install -y bind9 bind9-dnsutils unbound
sudo systemctl stop systemd-resolved 2>/dev/null # avoid port 53 conflict
sudo systemctl disable systemd-resolved 2>/dev/null
Part 1: Zone Authoring (20 min)
1.1 Create the zone file
sudo mkdir -p /etc/bind/zones /etc/bind/keys
cat << 'EOF' | sudo tee /etc/bind/zones/virtuslab.local
$ORIGIN virtuslab.local.
$TTL 300
@ IN SOA ns1.virtuslab.local. admin.virtuslab.local. (
2026052901 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
300 ) ; minimum TTL
@ IN NS ns1.virtuslab.local.
ns1 IN A 127.0.0.1
www IN A 192.168.100.10
api IN A 192.168.100.20
mail IN MX 10 mailserver.virtuslab.local.
mailserver IN A 192.168.100.30
EOF
1.2 Configure BIND with DNSSEC policy
# Generate keys
cd /etc/bind/keys
sudo dnssec-keygen -a ECDSAP256SHA256 -n ZONE virtuslab.local
sudo dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE virtuslab.local
sudo chown -R bind: /etc/bind/keys/
Add to /etc/bind/named.conf.local:
zone "virtuslab.local" {
type master;
file "/etc/bind/zones/virtuslab.local";
dnssec-policy default;
inline-signing yes;
key-directory "/etc/bind/keys";
};
sudo named-checkconf
sudo systemctl restart bind9
1.3 Verify signed responses
# Query the authoritative server
dig +dnssec www.virtuslab.local A @127.0.0.1
# Verify RRSIG is present
dig +dnssec +multi virtuslab.local DNSKEY @127.0.0.1
# View all DNSSEC records
dig +dnssec virtuslab.local SOA @127.0.0.1
dig +dnssec virtuslab.local NSEC3PARAM @127.0.0.1
Record:
- Is an RRSIG record present in the response to the www.virtuslab.local A query?
- What is the RRSIG's algorithm number and key tag?
- How many DNSKEY records are present? Which one is the KSK (look for flags=257)?
Part 2: Chain of Trust Verification (15 min)
2.1 Configure a trust anchor for the local zone
Since virtuslab.local is not in the global DNS hierarchy, we simulate the chain of trust by manually trusting the KSK:
# Extract the KSK's DS record
dig +short virtuslab.local DNSKEY @127.0.0.1 | grep " 257 " | \
dnssec-dsfromkey -f - virtuslab.local
Configure Unbound to use this DS hash as a trust anchor:
cat << 'EOF' | sudo tee /etc/unbound/unbound.conf.d/virtuslab.conf
server:
do-dnssec: yes
trust-anchor-signaling: no
verbosity: 1
stub-zone:
name: "virtuslab.local"
stub-addr: 127.0.0.1
# Add the DS record as a trust anchor (replace with actual DS output above)
# trust-anchor: "virtuslab.local. IN DS KEY_TAG ALG DIGEST_TYPE DIGEST"
EOF
sudo systemctl restart unbound
# Query through Unbound (port 5335 to avoid conflict with BIND on 53)
# Or configure Unbound on port 5335 for testing
dig +dnssec www.virtuslab.local A @127.0.0.1 -p 5335
Record:
- Does Unbound return the A record with the AD (Authenticated Data) bit set?
- What does the AD bit signify?
Part 3: Break the Chain of Trust (25 min)
3.1 Replace the KSK with a new key
# Generate a NEW KSK (different from the one whose DS record Unbound trusts)
cd /etc/bind/keys
sudo dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE -r /dev/urandom virtuslab.local
# Rename the original KSK to prevent BIND from using it
# Find the original KSK file (flags=257 in the .key file)
ls *.key | xargs grep -l "flags=257"
# Move the original KSK out
sudo mv Kvirtu...KSK-original.key Kvirtu...KSK-original.key.bak
sudo mv Kvirtu...KSK-original.private Kvirtu...KSK-original.private.bak
Force BIND to re-sign with the new KSK:
sudo rndc sign virtuslab.local
sudo rndc reload virtuslab.local
3.2 Observe validation failure
# Query through the validating resolver
dig +dnssec www.virtuslab.local A @127.0.0.1
# Expected: SERVFAIL - the new KSK does not match the trusted DS record
Record:
- What is the RCODE in the response from the validating resolver? (Expected: SERVFAIL)
- What does SERVFAIL mean for the user? Can they reach www.virtuslab.local?
- From the authoritative server directly (no validation): does the query succeed?
# Confirm the authoritative answer is still present
dig www.virtuslab.local A @127.0.0.1 +cd # +cd = disable validation
- What does
+cd(Checking Disabled) do? When would you use it in troubleshooting?
3.3 Restore the valid chain
# Restore the original KSK
sudo mv Kvirtu...KSK-original.key.bak Kvirtu...KSK-original.key
sudo mv Kvirtu...KSK-original.private.bak Kvirtu...KSK-original.private
# Remove the new KSK (or move it out)
# Re-sign
sudo rndc sign virtuslab.local
sudo rndc reload virtuslab.local
# Verify validation passes again
dig +dnssec www.virtuslab.local A @127.0.0.1
Part 4: DoT Forwarding (15 min)
Configure Unbound to forward queries to Cloudflare's DoT resolver:
cat << 'EOF' | sudo tee /etc/unbound/unbound.conf.d/dot-forward.conf
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 8.8.8.8@853#dns.google
EOF
sudo systemctl restart unbound
Capture the DoT connection:
sudo tcpdump -i lo -n "tcp port 853" -w /tmp/dot-capture.pcap &
dig google.com A @127.0.0.1
sudo kill %1
Record:
- In the DoT capture, confirm the DNS query is inside a TLS session (port 853). Is the DNS payload visible in plaintext?
- What TLS version is used for the DoT connection?
- A corporate firewall blocks port 853. What would happen to Unbound's DoT forwarding? What alternative protocol could bypass this block?
Lab Report
- Explain the difference between a ZSK and a KSK in DNSSEC. Why are two keys used rather than one?
- You break the DNSSEC chain and a validating resolver returns SERVFAIL. A non-validating resolver returns the A record normally. What does this tell you about DNSSEC's deployment model?
- DNSSEC ensures that DNS data is authentic. DoT ensures that DNS queries are private. Design a DNS architecture that achieves BOTH for a small enterprise. What components do you need?
Cleanup
sudo systemctl stop bind9 unbound
sudo systemctl restart systemd-resolved 2>/dev/null
Grading (10 points)
| Item | Points |
|---|---|
| Zone signed; RRSIG present in A and DNSKEY responses | 2 |
| Broken chain: SERVFAIL confirmed; explains cause correctly | 3 |
+cd flag behavior explained; authoritative query succeeds with +cd |
2 |
| DoT forwarding configured; port 853 TLS traffic confirmed in capture | 2 |
| Lab report: ZSK/KSK distinction; DNSSEC + DoT architecture | 1 |