Classroom Glossary Public page

Lab 5: DNSSEC Zone Authoring, Chain of Trust, and Validation Failure

524 words

Chapter: 5 (DNS)
Duration: 90 minutes
Tools: BIND 9 (named), dig, Unbound, dnssec-keygen
Points: 10


Objectives

  1. Author a DNSSEC-signed zone using BIND 9 inline signing
  2. Verify the chain of trust with dig +dnssec
  3. Deliberately break the DNSSEC chain and observe validation failure
  4. Configure Unbound as a validating resolver and verify it accepts/rejects the zone
  5. 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:

  1. Is an RRSIG record present in the response to the www.virtuslab.local A query?
  2. What is the RRSIG's algorithm number and key tag?
  3. 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:

  1. Does Unbound return the A record with the AD (Authenticated Data) bit set?
  2. 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:

  1. What is the RCODE in the response from the validating resolver? (Expected: SERVFAIL)
  2. What does SERVFAIL mean for the user? Can they reach www.virtuslab.local?
  3. 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
  1. 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:

  1. In the DoT capture, confirm the DNS query is inside a TLS session (port 853). Is the DNS payload visible in plaintext?
  2. What TLS version is used for the DoT connection?
  3. A corporate firewall blocks port 853. What would happen to Unbound's DoT forwarding? What alternative protocol could bypass this block?

Lab Report

  1. Explain the difference between a ZSK and a KSK in DNSSEC. Why are two keys used rather than one?
  2. 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?
  3. 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