Standalone BIND DNS Deployment (Headless)

1. Overview

Deploy standalone BIND DNS server on Rocky 9 using cloud images for fully headless deployment. This is the enterprise pattern - DNS as a dedicated, separated service.

1.1. Architecture Decision

Pattern Rationale

Standalone DNS

Separation of concerns - DNS independent of identity services

Cloud Images

Enterprise standard (AWS/Azure/GCP pattern)

Headless

No GUI, SSH-only management

Authoritative

Master DNS for inside.domusdigitalis.dev zone

1.2. DNS Architecture

BIND DNS Architecture

1.3. Environment

Component Value

KVM Hypervisor

kvm-01.inside.domusdigitalis.dev / supermicro300-9d1 (Arch Linux)

Guest OS

Rocky Linux 9 GenericCloud

BIND Hostname

bind-01.inside.domusdigitalis.dev

BIND IP

10.50.1.90

Zone

inside.domusdigitalis.dev

VM Storage

/mnt/onboard-ssd/vms/

2. Phase 1: Download Cloud Image

If not already downloaded:

cd /var/lib/libvirt/images
sudo curl -LO https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2

Verify download (~620MB):

ls -lh Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
Expected Output
-rw-r--r-- 1 root root 619M Feb 15 08:00 Rocky-9-GenericCloud-Base.latest.x86_64.qcow2

2.1. Verify Checksum (Security)

Always verify checksums - HTTPS only proves server identity, not file integrity. If Rocky’s servers were compromised, you’d download malware over valid HTTPS. Checksums prove the file matches what Rocky published.

Download the checksum file:

sudo curl -LO https://dl.rockylinux.org/pub/rocky/9/images/x86_64/CHECKSUM

Extract and verify (Rocky uses BSD-format checksums):

# Get expected hash from CHECKSUM file
grep "GenericCloud-Base.latest.x86_64.qcow2" CHECKSUM
Expected Output
# Rocky-9-GenericCloud-Base.latest.x86_64.qcow2: 648806400 bytes
SHA256 (Rocky-9-GenericCloud-Base.latest.x86_64.qcow2) = 15d81d3434b298142b2fdd8fb54aef2662684db5c082cc191c3c79762ed6360c
# Verify hash matches (converts BSD → GNU format)
EXPECTED=$(grep "GenericCloud-Base.latest.x86_64.qcow2)" CHECKSUM | awk '{print $4}')
echo "$EXPECTED  Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" | sha256sum -c
Expected Output
Rocky-9-GenericCloud-Base.latest.x86_64.qcow2: OK

If verification fails, DO NOT USE THE IMAGE. Re-download or investigate.

3. Phase 2: Create cloud-init Configuration

3.1. Create cloud-init Directory

mkdir -p /tmp/bind-01-cloud-init
cd /tmp/bind-01-cloud-init

3.2. meta-data

cat > meta-data << 'EOF'
instance-id: bind-01
local-hostname: bind-01
EOF

3.3. user-data

cat > user-data << 'EOF'
#cloud-config
hostname: bind-01
fqdn: bind-01.inside.domusdigitalis.dev
manage_etc_hosts: true

users:
  - name: evanusmodestus
    groups: wheel
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    lock_passwd: false
    plain_text_passwd: REPLACE_WITH_GOPASS_PASSWORD
    ssh_authorized_keys:
      # YubiKey primary
      - sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIFHfsGSAFAkqwYj6EGS9sA2MROjs28zM6LJds3gagsCkAAAACHNzaDpkMDAw evanusmodestus@d000-yubikey
      # YubiKey secondary
      - sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIEBZ+kus4aTHzQt1zNnEnGxJs+Lf56vrCdcyvqLhpp9hAAAACHNzaDpkMDAw evanusmodestus@d000-secondary

runcmd:
  # Configure static IP (interface is eth0 on Rocky cloud images)
  - nmcli connection delete 'Wired connection 1' 2>/dev/null || true
  - nmcli con add type ethernet ifname eth0 con-name mgmt ipv4.addresses {bind-ip}/24 ipv4.gateway {vyos-vip} ipv4.dns {vyos-vip} ipv4.method manual
  - nmcli con up mgmt
  - dnf install -y bind bind-utils

final_message: "Cloud-init completed. System ready for BIND configuration."
EOF

Password: Generate with gopass generate -s v2/DOMUS/servers/bind-01/evanusmodestus 24 and replace REPLACE_WITH_GOPASS_PASSWORD.

Interface Name: Rocky 9 cloud images use eth0, not enp1s0.

3.4. Create cloud-init ISO

# Install genisoimage if needed (Arch)
sudo pacman -S --noconfirm cdrtools

# Create ISO
genisoimage -output /mnt/onboard-ssd/vms/bind-01-cloud-init.iso \
  -volid cidata -joliet -rock meta-data user-data

4. Phase 3: Create VM Disk

# Copy base image
sudo cp /var/lib/libvirt/images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 \
  /mnt/onboard-ssd/vms/bind-01.qcow2
# Resize to 20GB (DNS doesn't need much)
sudo qemu-img resize /mnt/onboard-ssd/vms/bind-01.qcow2 20G

5. Phase 4: Create VM

sudo virt-install \
  --name bind-01 \
  --memory 2048 \
  --vcpus 2 \
  --disk path=/mnt/onboard-ssd/vms/bind-01.qcow2,format=qcow2 \
  --disk path=/mnt/onboard-ssd/vms/bind-01-cloud-init.iso,device=cdrom \
  --network bridge=virbr0,model=virtio \
  --os-variant rocky9 \
  --graphics none \
  --console pty,target_type=serial \
  --import \
  --noautoconsole

6. Phase 5: Verify VM Boot

# Check status
sudo virsh list --all | grep bind

# View console
sudo virsh console bind-01

Press Enter, login with evanusmodestus.

Exit console: Ctrl+]

7. Phase 6: DNS & DHCP Reservation

7.1. Get VM MAC

sudo virsh domiflist bind-01

7.2. VyOS DHCP Reservation

VyOS DHCP is configured via CLI. SSH to vyos-01 and add static mapping:

configure
set service dhcp-server shared-network-name MGMT subnet 10.50.1.0/24 static-mapping bind-01 ip-address 10.50.1.90
set service dhcp-server shared-network-name MGMT subnet 10.50.1.0/24 static-mapping bind-01 mac-address <MAC-FROM-ABOVE>
commit
save

7.3. DNS Record

DNS is handled by BIND itself. After bind-01 is operational, records are managed in /var/named/inside.domusdigitalis.dev.zone. VyOS forwards DNS to BIND.

8. Phase 7: Workstation Setup (Prerequisites)

Before configuring BIND, set up your workstation for SSH access.

8.1. Password Management (gopass)

Generate and store password for cloud-init:

gopass generate -s v2/DOMUS/servers/bind-01/evanusmodestus 24
gopass show v2/DOMUS/servers/bind-01/evanusmodestus

Copy to clipboard when needed:

gopass show -c v2/DOMUS/servers/bind-01/evanusmodestus

8.2. Workstation SSH Config

Add to ~/.ssh/config on your workstation:

cat >> ~/.ssh/config << 'EOF'
# ═══════════════════════════════════════════════════════════════════════════════════════
# HOME INFRASTRUCTURE - DNS & DIRECTORY SERVICES
# ═══════════════════════════════════════════════════════════════════════════════════════

Host bind-01
    HostName 10.50.1.90
    User evanusmodestus
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_secondary
    IdentityFile ~/.ssh/id_ed25519_d000
    PasswordAuthentication yes
    PreferredAuthentications publickey,password

Host bind-02
    HostName 10.50.1.91
    User evanusmodestus
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_secondary
    IdentityFile ~/.ssh/id_ed25519_d000
    PasswordAuthentication yes
    PreferredAuthentications publickey,password

Host ipa-01
    HostName 10.50.1.100
    User evanusmodestus
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_secondary
    IdentityFile ~/.ssh/id_ed25519_d000
    PasswordAuthentication yes
    PreferredAuthentications publickey,password

Host ipa-02
    HostName 10.50.1.101
    User evanusmodestus
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000
    IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_secondary
    IdentityFile ~/.ssh/id_ed25519_d000
    PasswordAuthentication yes
    PreferredAuthentications publickey,password
EOF

8.3. SSH Connection Test

ssh bind-01
Expected Output (YubiKey authentication)
Enter passphrase for key '/home/evanusmodestus/.ssh/id_ed25519_sk_rk_d000':
Confirm user presence for key ED25519-SK SHA256:UuOTOPAPIYYAG9sSW37hmAAHmCfKJuQ2eaQq6JSHILQ
User presence confirmed
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Sun Feb 15 06:54:26 2026
[evanusmodestus@bind-01 ~]$

9. Phase 8: Configure BIND

All commands in this phase run on bind-01 via SSH.

9.1. named.conf

sudo tee /etc/named.conf << 'EOF'
//
// BIND Configuration for inside.domusdigitalis.dev
//

options {
    listen-on port 53 { 127.0.0.1; 10.50.1.90; };
    listen-on-v6 { none; };
    directory       "/var/named";
    dump-file       "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    secroots-file   "/var/named/data/named.secroots";
    recursing-file  "/var/named/data/named.recursing";

    // Allow queries from management network
    allow-query     { localhost; 10.50.1.0/24; };

    // Forward external queries (upstream resolvers)
    forwarders {
        8.8.8.8;        // Google
        1.1.1.1;        // Cloudflare
    };

    recursion yes;
    dnssec-validation auto;

    // Security hardening
    version "not disclosed";
    allow-transfer { none; };
    rate-limit {
        responses-per-second 10;
        window 5;
    };

    managed-keys-directory "/var/named/dynamic";
    geoip-directory "/usr/share/GeoIP";

    pid-file "/run/named/named.pid";
    session-keyfile "/run/named/session.key";
};

logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };
};

// Root hints
zone "." IN {
    type hint;
    file "named.ca";
};

// Localhost zones
include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

// inside.domusdigitalis.dev zone
zone "inside.domusdigitalis.dev" IN {
    type master;
    file "inside.domusdigitalis.dev.zone";
    allow-update { none; };
};

// Reverse zone for 10.50.1.x
zone "1.50.10.in-addr.arpa" IN {
    type master;
    file "10.50.1.rev";
    allow-update { none; };
};
EOF

9.2. Forward Zone File

sudo tee /var/named/inside.domusdigitalis.dev.zone << 'EOF'
$TTL 86400
@   IN  SOA     bind-01.inside.domusdigitalis.dev. admin.inside.domusdigitalis.dev. (
                2026031101  ; Serial (YYYYMMDDNN)
                3600        ; Refresh
                1800        ; Retry
                604800      ; Expire
                86400 )     ; Minimum TTL

; Name servers
@               IN  NS      bind-01.inside.domusdigitalis.dev.

; Gateway - VyOS VRRP (.1-3)
vyos-vip        IN  A       10.50.1.1
vyos-01         IN  A       10.50.1.2
vyos-02         IN  A       10.50.1.3

; Network Devices (.10-19)
3560cx-01       IN  A       10.50.1.10
9300-01         IN  A       10.50.1.11

; Identity Services (.20-29)
ise-01          IN  A       10.50.1.20
ise-02          IN  A       10.50.1.21

; iPSK Manager (.30-39)
ipsk-mgr-01     IN  A       10.50.1.30
ipsk-mgr-02     IN  A       10.50.1.31

; Wireless Controllers (.40-49)
9800-wlc-01     IN  A       10.50.1.40
wlc-01          IN  A       10.50.1.40
9800-wlc-02     IN  A       10.50.1.41
wlc-02          IN  A       10.50.1.41

; Windows Servers (.50-59)
home-dc01       IN  A       10.50.1.50
home-dc02       IN  A       10.50.1.51

; PKI Services - Vault HA Cluster (.60-69)
vault-01        IN  A       10.50.1.60
vault-02        IN  A       10.50.1.61
vault-03        IN  A       10.50.1.62

; Storage/Git (.70-79)
nas-01          IN  A       10.50.1.70
nas-02          IN  A       10.50.1.71
gitea-01        IN  A       10.50.1.70

; IdP/SSO (.80-89)
keycloak-01     IN  A       10.50.1.80
keycloak-02     IN  A       10.50.1.81

; DNS Services (.90-99)
bind-01         IN  A       10.50.1.90
bind-02         IN  A       10.50.1.91

; LDAP/Directory (.100-109)
ipa-01          IN  A       10.50.1.100
ipa-02          IN  A       10.50.1.101

; KVM Hypervisors (.110-119)
kvm-01          IN  A       10.50.1.110
kvm-02          IN  A       10.50.1.111

; Kubernetes (.120-129)
k3s-master-01   IN  A       10.50.1.120
k3s-master-02   IN  A       10.50.1.121
k3s-master-03   IN  A       10.50.1.122

; Ingress & Monitoring (.130-139)
traefik-vip     IN  A       10.50.1.130
wazuh-indexer   IN  A       10.50.1.131
wazuh-manager   IN  A       10.50.1.134
grafana         IN  A       10.50.1.132
prometheus      IN  A       10.50.1.133
alertmanager    IN  A       10.50.1.135

; IPMI/BMC (.200-209)
ipmi-01         IN  A       10.50.1.200
ipmi-02         IN  A       10.50.1.201

; ============================================================================
; ACTIVE DIRECTORY SRV RECORDS (CRITICAL for Kerberos/LDAP)
; ============================================================================
; These records are REQUIRED for AD domain join, Kerberos authentication,
; and LDAP lookups. Windows DC auto-registers these via dynamic DNS.
; When migrating DNS to BIND, you MUST add these manually!
;
; Without these records:
;   - kinit fails: "Cannot find KDC for realm"
;   - Domain join fails
;   - AD group lookups fail (affects 802.1X authorization)
; ============================================================================

; Kerberos KDC
_kerberos._tcp          IN  SRV 0 100 88   home-dc01
_kerberos._udp          IN  SRV 0 100 88   home-dc01

; Kerberos password change
_kpasswd._tcp           IN  SRV 0 100 464  home-dc01
_kpasswd._udp           IN  SRV 0 100 464  home-dc01

; LDAP
_ldap._tcp              IN  SRV 0 100 389  home-dc01

; Global Catalog (for forest-wide searches)
_gc._tcp                IN  SRV 0 100 3268 home-dc01

; Domain Controller Locator (Microsoft-specific)
_ldap._tcp.dc._msdcs    IN  SRV 0 100 389  home-dc01
_kerberos._tcp.dc._msdcs IN SRV 0 100 88   home-dc01
_ldap._tcp.gc._msdcs    IN  SRV 0 100 3268 home-dc01

; Aliases
ise             IN  CNAME   ise-01
keycloak        IN  CNAME   keycloak-01
ipsk            IN  CNAME   ipsk-mgr-01
dc              IN  CNAME   home-dc01
ipa             IN  CNAME   ipa-01
dns             IN  CNAME   bind-01
vault           IN  CNAME   vault-01
nas             IN  CNAME   nas-01
gitea           IN  CNAME   gitea-01
wlc             IN  CNAME   9800-wlc-01
kvm             IN  CNAME   kvm-01
EOF

Windows DC DNS Migration Warning

When migrating DNS from Windows Server (AD-integrated DNS) to BIND, the AD SRV records above are NOT automatically created. Windows DCs register these records dynamically via secure DNS updates.

If you skip these records:

  • kinit fails with "Cannot find KDC for realm DOMAIN.COM"

  • Workstations cannot join the domain

  • ISE AD group authorization fails (802.1X policies don’t match)

  • LDAP lookups fail

Always verify after migration:

dig SRV _kerberos._tcp.inside.domusdigitalis.dev +short
dig SRV _ldap._tcp.inside.domusdigitalis.dev +short

9.3. Reverse Zone File

sudo tee /var/named/10.50.1.rev << 'EOF'
$TTL 86400
@   IN  SOA     bind-01.inside.domusdigitalis.dev. admin.inside.domusdigitalis.dev. (
                2026031101  ; Serial
                3600        ; Refresh
                1800        ; Retry
                604800      ; Expire
                86400 )     ; Minimum TTL

@               IN  NS      bind-01.inside.domusdigitalis.dev.

; Gateway - VyOS VRRP (.1-3)
1               IN  PTR     vyos-vip.inside.domusdigitalis.dev.
2               IN  PTR     vyos-01.inside.domusdigitalis.dev.
3               IN  PTR     vyos-02.inside.domusdigitalis.dev.

; Network Devices (.10-19)
10              IN  PTR     3560cx-01.inside.domusdigitalis.dev.
11              IN  PTR     9300-01.inside.domusdigitalis.dev.

; Identity Services (.20-29)
20              IN  PTR     ise-01.inside.domusdigitalis.dev.
21              IN  PTR     ise-02.inside.domusdigitalis.dev.

; iPSK Manager (.30-39)
30              IN  PTR     ipsk-mgr-01.inside.domusdigitalis.dev.
31              IN  PTR     ipsk-mgr-02.inside.domusdigitalis.dev.

; Wireless Controllers (.40-49)
40              IN  PTR     9800-wlc-01.inside.domusdigitalis.dev.
41              IN  PTR     9800-wlc-02.inside.domusdigitalis.dev.

; Windows Servers (.50-59)
50              IN  PTR     home-dc01.inside.domusdigitalis.dev.
51              IN  PTR     home-dc02.inside.domusdigitalis.dev.

; PKI Services - Vault HA Cluster (.60-69)
60              IN  PTR     vault-01.inside.domusdigitalis.dev.
61              IN  PTR     vault-02.inside.domusdigitalis.dev.
62              IN  PTR     vault-03.inside.domusdigitalis.dev.

; Storage/Git (.70-79)
70              IN  PTR     nas-01.inside.domusdigitalis.dev.
71              IN  PTR     nas-02.inside.domusdigitalis.dev.

; IdP/SSO (.80-89)
80              IN  PTR     keycloak-01.inside.domusdigitalis.dev.
81              IN  PTR     keycloak-02.inside.domusdigitalis.dev.

; DNS Services (.90-99)
90              IN  PTR     bind-01.inside.domusdigitalis.dev.
91              IN  PTR     bind-02.inside.domusdigitalis.dev.

; LDAP/Directory (.100-109)
100             IN  PTR     ipa-01.inside.domusdigitalis.dev.
101             IN  PTR     ipa-02.inside.domusdigitalis.dev.

; KVM Hypervisors (.110-119)
110             IN  PTR     kvm-01.inside.domusdigitalis.dev.
111             IN  PTR     kvm-02.inside.domusdigitalis.dev.

; Kubernetes (.120-129)
120             IN  PTR     k3s-master-01.inside.domusdigitalis.dev.
121             IN  PTR     k3s-master-02.inside.domusdigitalis.dev.
122             IN  PTR     k3s-master-03.inside.domusdigitalis.dev.

; Ingress & Monitoring (.130-139)
130             IN  PTR     traefik-vip.inside.domusdigitalis.dev.
131             IN  PTR     wazuh-indexer.inside.domusdigitalis.dev.
132             IN  PTR     grafana.inside.domusdigitalis.dev.
133             IN  PTR     prometheus.inside.domusdigitalis.dev.
134             IN  PTR     wazuh-manager.inside.domusdigitalis.dev.
135             IN  PTR     alertmanager.inside.domusdigitalis.dev.

; IPMI/BMC (.200-209)
200             IN  PTR     ipmi-01.inside.domusdigitalis.dev.
201             IN  PTR     ipmi-02.inside.domusdigitalis.dev.
EOF

9.4. Set Permissions

sudo chown root:named /var/named/inside.domusdigitalis.dev.zone /var/named/10.50.1.rev
sudo chmod 640 /var/named/inside.domusdigitalis.dev.zone /var/named/10.50.1.rev
ls -la /var/named/*.zone /var/named/*.rev
Expected Output
-rw-r-----. 1 root named 1850 Feb 15 12:00 /var/named/inside.domusdigitalis.dev.zone
-rw-r-----. 1 root named 1200 Feb 15 12:00 /var/named/10.50.1.rev

9.5. Validate Configuration

sudo named-checkconf /etc/named.conf
Expected Output
(no output = success)
sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone
Expected Output
zone inside.domusdigitalis.dev/IN: loaded serial 2026021401
OK
sudo named-checkzone 1.50.10.in-addr.arpa /var/named/10.50.1.rev
Expected Output
zone 1.50.10.in-addr.arpa/IN: loaded serial 2026021401
OK

10. Phase 9: Start BIND

sudo systemctl enable --now named
Expected Output
Created symlink /etc/systemd/system/multi-user.target.wants/named.service → /usr/lib/systemd/system/named.service.
sudo systemctl status named
Expected Output
● named.service - Berkeley Internet Name Domain (DNS)
     Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; preset: disabled)
     Active: active (running) since Sat 2026-02-15 ...
   Main PID: 12345 (named)
      Tasks: 5 (limit: 23081)
     Memory: 20.0M
        CPU: 50ms
     CGroup: /system.slice/named.service
             └─12345 /usr/sbin/named -u named -c /etc/named.conf

10.1. Configure Firewall

Rocky 9 GenericCloud images don’t include firewalld. Install and enable it first:

sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
Expected Output
(no output = already enabled by package installation)
sudo firewall-cmd --permanent --add-service=dns
Expected Output
success
sudo firewall-cmd --reload
Expected Output
success
sudo firewall-cmd --list-services
Expected Output
cockpit dhcpv6-client dns ssh

11. Phase 10: Test DNS

11.1. From bind-01 itself

dig @localhost ise-01.inside.domusdigitalis.dev +short
Expected Output
10.50.1.20
dig @localhost ise-01.inside.domusdigitalis.dev
Expected Output
; <<>> DiG 9.16.23-RH <<>> @localhost ise-01.inside.domusdigitalis.dev
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: ... (good)
;; QUESTION SECTION:
;ise-01.inside.domusdigitalis.dev. IN A

;; ANSWER SECTION:
ise-01.inside.domusdigitalis.dev. 86400 IN A 10.50.1.20

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Feb 15 12:00:00 UTC 2026
;; MSG SIZE  rcvd: 89

11.2. Reverse Lookup (PTR)

dig @localhost -x 10.50.1.20 +short
Expected Output
ise-01.inside.domusdigitalis.dev.
dig @localhost -x 10.50.1.20
Expected Output
; <<>> DiG 9.16.23-RH <<>> @localhost -x 10.50.1.20
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;20.1.50.10.in-addr.arpa.       IN      PTR

;; ANSWER SECTION:
20.1.50.10.in-addr.arpa. 86400  IN      PTR     ise-01.inside.domusdigitalis.dev.

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Feb 15 12:00:00 UTC 2026
;; MSG SIZE  rcvd: 100

11.3. Test CNAME Aliases

dig @localhost ise.inside.domusdigitalis.dev +short
Expected Output
ise-01.inside.domusdigitalis.dev.
10.50.1.20

11.4. From another host (workstation)

dig @10.50.1.90 ise-01.inside.domusdigitalis.dev +short
Expected Output
{ise-01-ip}

11.5. Test recursion (external domains)

dig @10.50.1.90 google.com +short
Expected Output
142.250.xxx.xxx

11.6. Verify Security Hardening

dig @localhost version.bind chaos txt +short
Expected Output
"not disclosed"

12. Phase 11: Configure VyOS DNS Forwarding

Configure VyOS to forward DNS queries to BIND (bind-01/bind-02).

VyOS acts as a DNS forwarder, sending all queries to BIND. BIND handles both authoritative zones (inside.domusdigitalis.dev) and recursive queries (external domains).

12.1. Via VyOS CLI

SSH to vyos-01 and configure DNS forwarding:

ssh vyos-01
configure
set service dns forwarding listen-address 10.50.1.1
set service dns forwarding name-server 10.50.1.90
set service dns forwarding name-server 10.50.1.91
set service dns forwarding allow-from 10.50.0.0/16
commit
save

12.2. Verify DNS Forwarding

From your workstation, test that VyOS forwards to BIND:

dig @10.50.1.1 ise-01.inside.domusdigitalis.dev +short
Expected Output
10.50.1.20

This confirms VyOS is forwarding DNS queries to BIND (bind-01/bind-02).

13. Zone Management

13.1. Adding New Records

Edit zone file and increment serial:

sudo vi /var/named/inside.domusdigitalis.dev.zone
# Update serial (YYYYMMDDNN)
# Add new records
# Reload zone
sudo rndc reload inside.domusdigitalis.dev

13.2. Verify Changes

dig @localhost newhost.inside.domusdigitalis.dev

14. Troubleshooting

14.1. Check BIND Logs

sudo journalctl -u named -f

14.2. Query Debug

dig @localhost +trace ise-01.inside.domusdigitalis.dev

14.3. Permission Issues

sudo ls -la /var/named/
# All zone files should be owned by root:named, mode 640

14.4. DNS Queries "Refused" from Non-MGMT VLANs

If DNS works from VLAN 100 (MGMT) but fails from VLAN 10 (DATA), VLAN 20 (VOICE), etc., this is NOT a firewall issue - it’s a BIND ACL issue.

14.4.1. Symptoms

  • DNS resolution works from MGMT network (10.50.1.0/24)

  • DNS resolution fails from employee VLANs (DATA, VOICE, etc.)

  • dig @10.50.1.90 google.com returns nothing or times out

  • Pinging 8.8.8.8 works (internet path is fine)

  • VyOS firewall logs show NO drops (traffic is allowed)

14.4.2. Diagnosis

Step 1: Capture traffic on VyOS:

# On VyOS
sudo tcpdump -i eth0.10 port 53 -n

Step 2: Test from VLAN 10 (another terminal):

dig @10.50.1.90 google.com

Step 3: Watch tcpdump output:

If you see "Refused" - this is the smoking gun:
01:29:12.881867 IP 10.50.10.102.62677 > 10.50.1.91.53: 515+ A? CISCO-CAPWAP-CONTROLLER.inside.domusdigitalis.dev. (67)
01:29:12.881868 IP 10.50.10.102.62677 > 10.50.1.90.53: 515+ A? CISCO-CAPWAP-CONTROLLER.inside.domusdigitalis.dev. (67)
01:29:12.882457 IP 10.50.1.90.53 > 10.50.10.102.62620: 41284 Refused- 0/0/0 (67)
01:29:12.883036 IP 10.50.1.90.53 > 10.50.10.102.62677: 515 Refused- 0/0/0 (67)

Key observation:

  • Query goes OUT: 10.50.10.102 > 10.50.1.90.53 (client → BIND)

  • Response comes BACK: 10.50.1.90.53 > 10.50.10.102 (BIND → client)

  • Response says: Refused (not timeout, not NXDOMAIN)

This proves: BIND is receiving the query, but REFUSING it due to ACL.

14.4.3. Root Cause

Default BIND config restricts queries to MGMT only:

grep -n "allow-query" /etc/named.conf
Typical broken config:
allow-query     { localhost; 10.50.1.0/24; };

Only MGMT (10.50.1.0/24) is allowed. Other VLANs are refused.

14.4.4. Fix

Add employee VLANs to allow-query:

sudo sed -i '/allow-query/s|{ localhost; 10.50.1.0/24; }|{ localhost; 10.50.1.0/24; 10.50.10.0/24; 10.50.20.0/24; 10.50.110.0/24; 10.50.120.0/24; }|' /etc/named.conf

Verify the change:

grep "allow-query" /etc/named.conf
Expected output:
allow-query     { localhost; 10.50.1.0/24; 10.50.10.0/24; 10.50.20.0/24; 10.50.110.0/24; 10.50.120.0/24; };

Validate and reload:

sudo named-checkconf && sudo systemctl reload named

Test from VLAN 10:

dig @10.50.1.90 google.com +short

14.4.5. VLAN ACL Reference

VLAN Subnet DNS Access?

MGMT (100)

10.50.1.0/24

✓ Yes

DATA (10)

10.50.10.0/24

✓ Yes (employees)

VOICE (20)

10.50.20.0/24

✓ Yes (VoIP needs DNS)

GUEST (30)

10.50.30.0/24

✗ No (use public DNS)

IOT (40)

10.50.40.0/24

✗ No (isolated)

SECURITY (110)

10.50.110.0/24

✓ Yes (ISE, Vault)

SERVICES (120)

10.50.120.0/24

✓ Yes (k8s services)

14.4.6. Key Learning

When debugging "DNS not working":

  1. Check firewall first - enable logging, look for drops

  2. If no drops, capture packets - tcpdump on VyOS interface

  3. Look for "Refused" - BIND ACL, not firewall

  4. Check allow-query - most common cause

14.5. Kerberos "Cannot find KDC" After DNS Migration

This is the #1 issue when migrating from Windows DC DNS to BIND. If you see this error, AD SRV records are missing from your zone file.

14.5.1. Symptoms

  • kinit fails immediately:

    kinit: Cannot find KDC for realm "INSIDE.DOMUSDIGITALIS.DEV" while getting initial credentials
  • 802.1X authentication succeeds (EAP-TLS) but AD group authorization rules don’t match

  • Domain join fails

  • LDAP tools can’t find domain controllers

14.5.2. Root Cause

Windows Domain Controllers automatically register SRV records via dynamic DNS updates. When you migrate to BIND:

  1. BIND doesn’t accept dynamic updates from Windows (unless configured with TSIG)

  2. You must manually add all AD SRV records to your zone file

  3. Common oversight: migrating A/PTR records but forgetting SRV records

14.5.3. Diagnosis

Check if SRV records exist:

# Query Kerberos SRV (should return DC hostname and port)
dig SRV _kerberos._tcp.inside.domusdigitalis.dev +short
Expected Output (working)
0 100 88 home-dc01.inside.domusdigitalis.dev.
Broken Output (missing records)
(empty - no output)

Or NXDOMAIN:

dig SRV _kerberos._tcp.inside.domusdigitalis.dev
Broken Output
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN

Check LDAP SRV:

dig SRV _ldap._tcp.inside.domusdigitalis.dev +short

14.5.4. Fix

  1. SSH to bind-01:

    ssh bind-01
  2. Edit the zone file:

    sudo vi /var/named/inside.domusdigitalis.dev.zone
  3. Add AD SRV records (before aliases section):

    ; Active Directory SRV Records (CRITICAL for Kerberos/LDAP)
    _kerberos._tcp          IN  SRV 0 100 88   home-dc01
    _kerberos._udp          IN  SRV 0 100 88   home-dc01
    _kpasswd._tcp           IN  SRV 0 100 464  home-dc01
    _kpasswd._udp           IN  SRV 0 100 464  home-dc01
    _ldap._tcp              IN  SRV 0 100 389  home-dc01
    _gc._tcp                IN  SRV 0 100 3268 home-dc01
    _ldap._tcp.dc._msdcs    IN  SRV 0 100 389  home-dc01
    _kerberos._tcp.dc._msdcs IN SRV 0 100 88   home-dc01
    _ldap._tcp.gc._msdcs    IN  SRV 0 100 3268 home-dc01
  4. Increment the serial number (YYYYMMDDNN format):

    2026021501  ; Was 2026021401
  5. Validate and reload:

    sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone
    sudo rndc reload inside.domusdigitalis.dev
  6. Clear resolver cache (if using pfSense Unbound as upstream):

    # On pfSense via SSH or shell
    pfctl -F all  # Flush states
    # Or via GUI: Services > DNS Resolver > Click "Save" to restart
  7. Verify fix:

    # From workstation
    dig SRV _kerberos._tcp.inside.domusdigitalis.dev +short
    kinit evanusmodestus@INSIDE.DOMUSDIGITALIS.DEV

    Should now prompt for password instead of "Cannot find KDC".

14.5.5. Prevention

When migrating DNS from Windows to BIND, always:

  1. Export all DNS records from Windows DNS Manager (including SRV)

  2. Check for _msdcs, _tcp, _udp subdomains

  3. Run nltest /dclist:DOMAIN on a Windows machine to see expected DC records

  4. Verify SRV records immediately after migration with dig SRV

VyOS forwards all DNS queries to BIND:

Domain Override Flow
Figure 1. Domain Override Flow

Clients query VyOS (10.50.1.1) → VyOS forwards to BIND (10.50.1.90/91) → BIND responds.

Cache issue: VyOS DNS forwarder caches responses. After adding SRV records to BIND, you may need to restart DNS forwarding on VyOS to clear the cache:

ssh vyos-01 "restart dns forwarding"

14.6. Console Blank (No Output)

Rocky 9 cloud images don’t enable serial console by default. Add VNC graphics:

# On kvm-01
sudo virsh destroy bind-01
sudo virt-xml bind-01 --add-device --graphics vnc,listen=0.0.0.0
sudo virsh start bind-01

# Get VNC port
sudo virsh vncdisplay bind-01

Access via Cockpit: kvm-01.inside.domusdigitalis.dev:9090 → Virtual Machines → bind-01 → Console

14.7. Manual Network Setup (If cloud-init Fails)

If cloud-init didn’t configure networking, set it up manually via console.

14.7.1. Step 1: Check Current State

# Check device status
nmcli device status
Expected Output
DEVICE  TYPE      STATE                   CONNECTION
eth0    ethernet  connected               System eth0
lo      loopback  connected (externally)  lo
# Check interface name (Rocky 9 cloud images use eth0, NOT enp1s0)
ip link

14.7.2. Step 2: Delete Existing Connection (if wrong config)

# Delete any incorrectly configured connection
sudo nmcli con delete mgmt 2>/dev/null
sudo nmcli connection delete 'Wired connection 1' 2>/dev/null

14.7.3. Step 3: Configure Static IP

sudo nmcli con add type ethernet ifname eth0 con-name mgmt \
  ipv4.addresses 10.50.1.90/24 \
  ipv4.gateway 10.50.1.1 \
  ipv4.dns 10.50.1.1 \
  ipv4.method manual
Expected Output
Connection 'mgmt' (uuid-here) successfully added.

14.7.4. Step 4: Activate Connection

sudo nmcli con up mgmt

14.7.5. Step 5: Verify Configuration

# Show connection details
nmcli con show mgmt | grep -E "ipv4\.(addr|gate|dns)"
Expected Output
ipv4.addresses:                         {bind-ip}/24
ipv4.gateway:                           {vyos-vip}
ipv4.dns:                               {vyos-vip}
# Show active IP (terse format)
nmcli -g IP4 device show eth0
Expected Output
IP4:{bind-ip}/24:{vyos-vip}:dst = {mgmt-network}, nh = 0.0.0.0, mt = 100 | dst = 0.0.0.0/0, nh = {vyos-vip}, mt = 100:{vyos-vip}:::
# Test connectivity
ping -c2 10.50.1.1

14.8. Add YubiKey SSH Keys (Manual)

If SSH keys weren’t added by cloud-init:

mkdir -p ~/.ssh && chmod 700 ~/.ssh

cat >> ~/.ssh/authorized_keys << 'EOF'
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIFHfsGSAFAkqwYj6EGS9sA2MROjs28zM6LJds3gagsCkAAAACHNzaDpkMDAw evanusmodestus@d000-yubikey
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIEBZ+kus4aTHzQt1zNnEnGxJs+Lf56vrCdcyvqLhpp9hAAAACHNzaDpkMDAw evanusmodestus@d000-secondary
EOF

chmod 600 ~/.ssh/authorized_keys

Appendix A: DNS Fundamentals for Security Professionals

This appendix provides deep DNS knowledge essential for security operations, incident response, and infrastructure defense.

A.1. DNS Record Types Reference

Type Name Purpose

A

Address

Maps hostname to IPv4 address

AAAA

IPv6 Address

Maps hostname to IPv6 address

CNAME

Canonical Name

Alias pointing to another hostname (cannot coexist with other records at same name)

MX

Mail Exchange

Mail server with priority (lower = preferred)

NS

Name Server

Authoritative DNS servers for zone

PTR

Pointer

Reverse lookup (IP → hostname)

SOA

Start of Authority

Zone metadata (serial, refresh, retry, expire, TTL)

TXT

Text

Arbitrary text (SPF, DKIM, DMARC, domain verification)

SRV

Service

Service location with port and priority (_ldap._tcp, _kerberos._udp)

CAA

Certificate Authority Authorization

Which CAs can issue certificates for domain

TLSA

TLS Authentication

DANE certificate pinning

SSHFP

SSH Fingerprint

SSH host key fingerprints for DNSSEC-validated verification

A.1.1. Security-Relevant Record Types

Record Security Use

TXT (SPF)

v=spf1 mx ip4:10.50.1.0/24 -all - Email sender authorization

TXT (DKIM)

Public key for email signature verification

TXT (DMARC)

v=DMARC1; p=reject; rua=mailto:dmarc@domain - Email policy

CAA

0 issue "letsencrypt.org" - Restrict certificate issuance

TLSA

Certificate pinning via DANE

SSHFP

ssh-keygen -r hostname - Verify SSH host keys via DNS

A.2. Zone File Syntax Deep Dive

A.2.1. SOA Record Breakdown

@   IN  SOA bind-01.inside.domusdigitalis.dev. hostmaster.inside.domusdigitalis.dev. (
            2026021501  ; Serial (YYYYMMDDNN) - MUST increment on every change
            3600        ; Refresh (1 hour) - How often slaves check for updates
            900         ; Retry (15 min) - Retry interval if refresh fails
            604800      ; Expire (7 days) - Slave stops answering if no master contact
            86400       ; Minimum TTL (1 day) - Negative caching TTL (NXDOMAIN)
        )

Serial number is critical. If you edit a zone and forget to increment the serial, slaves won’t pick up changes. Use YYYYMMDDNN format (e.g., 2026021501 = Feb 15, 2026, change #01).

A.2.2. Record Syntax Patterns

; Absolute name (ends with dot - FQDN)
bind-01.inside.domusdigitalis.dev.  IN  A  10.50.1.90

; Relative name (no dot - zone suffix appended automatically)
bind-01     IN  A  10.50.1.90
; Becomes: bind-01.inside.domusdigitalis.dev.

; @ symbol = zone origin (the zone name itself)
@           IN  NS  bind-01.inside.domusdigitalis.dev.

; Wildcards
*.lab       IN  A   10.50.2.100
; Matches: anything.lab.inside.domusdigitalis.dev

A.2.3. Common Mistakes

Mistake Consequence

Missing trailing dot on FQDN

bind-01.inside.domusdigitalis.dev becomes bind-01.inside.domusdigitalis.dev.inside.domusdigitalis.dev.

CNAME at zone apex

Breaks NS/MX records; use ALIAS/ANAME if needed

Duplicate records

Causes round-robin (may be intentional for load balancing)

Forgot to increment serial

Slaves don’t update; inconsistent responses

A.3. DNS Query Anatomy

A.3.1. Query Flow

DNS Query Flow
Figure 2. DNS Query Flow

A.3.2. Query Flags Explained

dig ise-01.inside.domusdigitalis.dev
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
Flag Meaning

QR

Query Response (1 = response, 0 = query)

AA

Authoritative Answer (server is authoritative for zone)

RD

Recursion Desired (client wants recursive resolution)

RA

Recursion Available (server supports recursion)

AD

Authenticated Data (DNSSEC validated)

CD

Checking Disabled (DNSSEC validation skipped)

A.4. Essential dig Commands

A.4.1. Basic Queries

# Simple A record lookup
dig host.domain.com

# Specific record type
dig MX domain.com
dig TXT domain.com
dig ANY domain.com  # All records (often blocked)

# Short output (just the answer)
dig +short host.domain.com

# Reverse lookup
dig -x 10.50.1.50

A.4.2. Targeting Specific Servers

# Query specific DNS server
dig @10.50.1.90 host.domain.com

# Query authoritative server directly (bypass cache)
dig @bind-01.inside.domusdigitalis.dev host.inside.domusdigitalis.dev

# Compare responses from different servers
dig @10.50.1.1 host.domain.com +short   # VyOS forwarder (forwards to BIND)
dig @10.50.1.90 host.domain.com +short  # BIND directly
dig @8.8.8.8 host.domain.com +short     # Google (external view)

A.4.3. Tracing Resolution Path

# Full trace from root servers
dig +trace google.com

# Trace with DNSSEC info
dig +trace +dnssec google.com

A.4.4. Security-Focused Queries

# Check DNSSEC status
dig +dnssec domain.com

# Query for DNSKEY (DNSSEC public key)
dig DNSKEY domain.com

# Check CAA records (certificate authority restrictions)
dig CAA domain.com

# Check SPF record
dig TXT domain.com | grep spf

# Check DMARC policy
dig TXT _dmarc.domain.com

# Check DKIM selector
dig TXT selector._domainkey.domain.com

# Check SSH fingerprints (if published)
dig SSHFP host.domain.com

A.4.5. Debugging and Forensics

# Full verbose output with timing
dig +stats +answer host.domain.com

# Show query time and server
dig host.domain.com | grep -E "Query time|SERVER"

# TCP query (useful when UDP blocked or large responses)
dig +tcp host.domain.com

# Specify source port (firewall testing)
dig -b 0.0.0.0#54321 host.domain.com

# EDNS client subnet (geolocation testing)
dig +subnet=203.0.113.0/24 cdn.domain.com

A.5. DNS Attack Vectors and Defenses

A.5.1. Attack: DNS Cache Poisoning

Attacker injects false records into resolver cache.

Defense Implementation

Source port randomization

Modern resolvers do this automatically

DNSSEC validation

dnssec-validation auto; in named.conf

Query ID randomization

Enabled by default in BIND 9

0x20 encoding

Case randomization (Unbound supports this)

A.5.2. Attack: DNS Amplification (DDoS)

Attacker spoofs victim’s IP, sends queries to open resolvers; responses flood victim.

Defense Implementation

Rate limiting

rate-limit { responses-per-second 10; };

Disable open recursion

allow-recursion { localhost; 10.50.0.0/16; };

Response Rate Limiting (RRL)

Built into BIND 9.9+

BCP38 (source address validation)

Network-level anti-spoofing

A.5.3. Attack: DNS Tunneling / Exfiltration

Data exfiltration via DNS queries (e.g., base64data.evil.com).

Detection Method

Query length anomalies

Normal hostnames < 30 chars; tunneling often > 100

High query volume

Unusual QPS to single domain

Entropy analysis

Tunneled data has high entropy

TXT record abuse

Large TXT responses to suspicious domains

# Monitor for suspicious query patterns
sudo journalctl -u named -f | grep -E "query.*\.(tk|ml|ga|cf|gq)"

# Check for unusually long queries in BIND query log
awk 'length($0) > 200' /var/log/named/queries.log

A.5.4. Attack: Zone Transfer Exploitation

Attacker retrieves entire zone contents via AXFR.

Defense Implementation

Restrict transfers

allow-transfer { 10.50.1.91; }; (slaves only)

TSIG authentication

Shared secret for zone transfers

Disable for public zones

allow-transfer { none; };

# Test if zone transfer is allowed (should fail)
dig @bind-01.inside.domusdigitalis.dev inside.domusdigitalis.dev AXFR
Expected Output (secure)
; Transfer failed.

A.5.5. Attack: Subdomain Takeover

Dangling CNAME pointing to decommissioned service (S3, Azure, Heroku).

Defense Method

Audit CNAME records

Regular checks for orphaned CNAMEs

Remove before decommission

Delete DNS records BEFORE releasing cloud resources

Monitor for changes

Alert on CNAME additions

# Find all CNAME records in zone
dig @bind-01 inside.domusdigitalis.dev AXFR | grep CNAME

A.6. BIND Security Hardening Checklist

Control Configuration Status

Hide version

version "not disclosed";

[ ]

Restrict recursion

allow-recursion { trusted; };

[ ]

Disable zone transfers

allow-transfer { slaves; };

[ ]

Enable rate limiting

rate-limit { responses-per-second 10; };

[ ]

DNSSEC validation

dnssec-validation auto;

[ ]

Disable unnecessary features

allow-update { none; };

[ ]

Chroot BIND

named -t /var/named/chroot

[ ]

Minimal zone exposure

Don’t publish internal hosts externally

[ ]

Log queries

querylog yes;

[ ]

Log security events

category security { security_log; };

[ ]

A.7. Logging and Monitoring

A.7.1. Enable Query Logging

logging {
    channel queries_log {
        file "/var/log/named/queries.log" versions 5 size 50M;
        severity info;
        print-time yes;
        print-category yes;
    };

    channel security_log {
        file "/var/log/named/security.log" versions 3 size 10M;
        severity dynamic;
        print-time yes;
        print-severity yes;
    };

    category queries { queries_log; };
    category security { security_log; };
    category dnssec { security_log; };
};

A.7.2. Create Log Directory

sudo mkdir -p /var/log/named
sudo chown named:named /var/log/named
sudo chmod 750 /var/log/named

A.7.3. Toggle Query Logging

# Enable query logging dynamically
sudo rndc querylog on

# Disable (reduces disk I/O in production)
sudo rndc querylog off

# Check current status
sudo rndc status | grep "query logging"

A.7.4. Log Analysis

# Top queried domains
awk '{print $4}' /var/log/named/queries.log | sort | uniq -c | sort -rn | head -20

# Queries by client IP
awk '{print $6}' /var/log/named/queries.log | sort | uniq -c | sort -rn | head -20

# Failed queries (NXDOMAIN)
grep "NXDOMAIN" /var/log/named/queries.log | awk '{print $4}' | sort | uniq -c | sort -rn

# Potential tunneling (long queries)
awk 'length($4) > 50 {print $4}' /var/log/named/queries.log | sort | uniq -c

A.8. DNSSEC Overview

DNSSEC provides authentication and integrity for DNS responses through cryptographic signatures.

A.8.1. DNSSEC Record Types

Record Purpose

DNSKEY

Public key for zone signing

RRSIG

Signature over a record set

DS

Delegation Signer (hash of child DNSKEY in parent zone)

NSEC/NSEC3

Authenticated denial of existence

A.8.2. Validate DNSSEC

# Check if domain has DNSSEC
dig +dnssec cloudflare.com

# Look for AD (Authenticated Data) flag
dig cloudflare.com | grep "flags:"

# Query DNSKEY records
dig DNSKEY cloudflare.com +short

# Trace DNSSEC chain
dig +trace +dnssec cloudflare.com

A.8.3. DNSSEC for Internal Zones

For internal zones, DNSSEC adds complexity without external validation benefit. Consider:

  • Skip for internal-only zones (like inside.domusdigitalis.dev)

  • Enable validation for external queries (dnssec-validation auto;)

  • Sign zones exposed to internet (if applicable)

A.9. DNS over TLS/HTTPS (DoT/DoH)

Modern encrypted DNS protocols prevent eavesdropping on queries.

Protocol Port Use Case

DNS (traditional)

53/UDP, 53/TCP

Internal networks, LANs

DoT (DNS over TLS)

853/TCP

Encrypted to trusted resolver

DoH (DNS over HTTPS)

443/TCP

Encrypted, bypasses network monitoring

A.9.1. VyOS DNS over TLS (Future)

VyOS DNS forwarding currently uses standard UDP/TCP. For encrypted DNS:

  • Use BIND as the recursive resolver with dnssec-validation auto

  • Consider deploying a dedicated DoT/DoH proxy (e.g., dnscrypt-proxy, stubby)

  • BIND 9.18+ supports DNS-over-TLS natively

A.10. SRV Records (Service Discovery)

SRV records are critical for Active Directory, Kerberos, LDAP, SIP, and other service discovery.

A.10.1. SRV Record Format

_service._protocol.name.  TTL  IN  SRV  priority  weight  port  target.
Field Description

_service

Service name (e.g., _ldap, _kerberos, _sip)

_protocol

_tcp or _udp

priority

Lower = preferred (like MX)

weight

Load balancing among same priority (higher = more traffic)

port

Service port number

target

FQDN of server providing service

A.10.2. Active Directory SRV Records

; Kerberos
_kerberos._tcp.inside.domusdigitalis.dev.     IN SRV 0 100 88  home-dc01.inside.domusdigitalis.dev.
_kerberos._udp.inside.domusdigitalis.dev.     IN SRV 0 100 88  home-dc01.inside.domusdigitalis.dev.
_kpasswd._tcp.inside.domusdigitalis.dev.      IN SRV 0 100 464 home-dc01.inside.domusdigitalis.dev.
_kpasswd._udp.inside.domusdigitalis.dev.      IN SRV 0 100 464 home-dc01.inside.domusdigitalis.dev.

; LDAP
_ldap._tcp.inside.domusdigitalis.dev.         IN SRV 0 100 389 home-dc01.inside.domusdigitalis.dev.
_ldap._tcp.dc._msdcs.inside.domusdigitalis.dev. IN SRV 0 100 389 home-dc01.inside.domusdigitalis.dev.

; Global Catalog
_gc._tcp.inside.domusdigitalis.dev.           IN SRV 0 100 3268 home-dc01.inside.domusdigitalis.dev.
_ldap._tcp.gc._msdcs.inside.domusdigitalis.dev. IN SRV 0 100 3268 home-dc01.inside.domusdigitalis.dev.

A.10.3. FreeIPA SRV Records

; Kerberos (FreeIPA)
_kerberos._tcp.inside.domusdigitalis.dev.     IN SRV 0 100 88  ipa-01.inside.domusdigitalis.dev.
_kerberos._udp.inside.domusdigitalis.dev.     IN SRV 0 100 88  ipa-01.inside.domusdigitalis.dev.
_kerberos-master._tcp.inside.domusdigitalis.dev. IN SRV 0 100 88 ipa-01.inside.domusdigitalis.dev.
_kpasswd._tcp.inside.domusdigitalis.dev.      IN SRV 0 100 464 ipa-01.inside.domusdigitalis.dev.

; LDAP (FreeIPA)
_ldap._tcp.inside.domusdigitalis.dev.         IN SRV 0 100 389 ipa-01.inside.domusdigitalis.dev.

; NTP (FreeIPA provides time sync)
_ntp._udp.inside.domusdigitalis.dev.          IN SRV 0 100 123 ipa-01.inside.domusdigitalis.dev.

A.10.4. Other Common SRV Records

; SIP (VoIP)
_sip._tcp.domain.com.       IN SRV 10 60 5060 sip-server.domain.com.
_sip._udp.domain.com.       IN SRV 10 60 5060 sip-server.domain.com.
_sips._tcp.domain.com.      IN SRV 10 60 5061 sip-server.domain.com.

; XMPP (Jabber/Chat)
_xmpp-client._tcp.domain.com. IN SRV 5 0 5222 xmpp.domain.com.
_xmpp-server._tcp.domain.com. IN SRV 5 0 5269 xmpp.domain.com.

; CalDAV / CardDAV (Calendar/Contacts)
_caldavs._tcp.domain.com.   IN SRV 0 0 443 calendar.domain.com.
_carddavs._tcp.domain.com.  IN SRV 0 0 443 contacts.domain.com.

; IMAP / Submission (Email)
_imaps._tcp.domain.com.     IN SRV 0 0 993 mail.domain.com.
_submission._tcp.domain.com. IN SRV 0 0 587 mail.domain.com.

A.10.5. Verify SRV Records

# Query SRV record
dig SRV _ldap._tcp.inside.domusdigitalis.dev

# Query Kerberos SRV
dig SRV _kerberos._tcp.inside.domusdigitalis.dev +short

A.11. Dynamic DNS with TSIG

TSIG (Transaction Signature) enables secure dynamic DNS updates from DHCP servers or clients.

A.11.1. Generate TSIG Key

# Generate HMAC-SHA256 key
tsig-keygen -a hmac-sha256 dhcp-update > /etc/named/dhcp-update.key
Example Output (/etc/named/dhcp-update.key)
key "dhcp-update" {
    algorithm hmac-sha256;
    secret "Base64EncodedSecretHere==";
};

A.11.2. Configure BIND for Dynamic Updates

// Include TSIG key
include "/etc/named/dhcp-update.key";

zone "inside.domusdigitalis.dev" IN {
    type master;
    file "/var/named/dynamic/inside.domusdigitalis.dev.zone";
    allow-update { key dhcp-update; };  // Only TSIG-signed updates
};

zone "1.50.10.in-addr.arpa" IN {
    type master;
    file "/var/named/dynamic/1.50.10.in-addr.arpa.zone";
    allow-update { key dhcp-update; };
};

A.11.3. Configure DHCP Server (ISC DHCP)

# /etc/dhcp/dhcpd.conf
key dhcp-update {
    algorithm hmac-sha256;
    secret "Base64EncodedSecretHere==";
};

zone inside.domusdigitalis.dev. {
    primary 10.50.1.90;
    key dhcp-update;
}

zone 1.50.10.in-addr.arpa. {
    primary 10.50.1.90;
    key dhcp-update;
}

A.11.4. Manual Dynamic Update (nsupdate)

# Create update file
cat > /tmp/dns-update.txt << 'EOF'
server 10.50.1.90
zone inside.domusdigitalis.dev
update add newhost.inside.domusdigitalis.dev. 3600 A 10.50.1.200
send
EOF

# Execute with TSIG key
nsupdate -k /etc/named/dhcp-update.key /tmp/dns-update.txt

A.11.5. Verify Dynamic Update

# Check journal file (dynamic updates stored here)
ls -la /var/named/dynamic/*.jnl

# Force journal to zone file
sudo rndc sync inside.domusdigitalis.dev

A.12. Split-Horizon DNS (Views)

Views allow different responses based on client source IP - essential for internal vs external DNS.

A.12.1. View Configuration

// Define ACLs first
acl "internal" {
    10.50.0.0/16;
    192.168.0.0/16;
    127.0.0.1;
};

acl "external" {
    any;
};

// Internal view (full access)
view "internal" {
    match-clients { internal; };
    recursion yes;

    zone "inside.domusdigitalis.dev" IN {
        type master;
        file "/var/named/internal/inside.domusdigitalis.dev.zone";
    };

    zone "domusdigitalis.dev" IN {
        type master;
        file "/var/named/internal/domusdigitalis.dev.zone";
    };
};

// External view (limited, no internal hosts)
view "external" {
    match-clients { external; };
    recursion no;  // Never recurse for external clients

    zone "domusdigitalis.dev" IN {
        type master;
        file "/var/named/external/domusdigitalis.dev.zone";
        // Different zone file with only public records
    };
};

A.12.2. Directory Structure for Views

BIND Views Structure
Figure 3. BIND Views Structure

A.13. Response Policy Zones (RPZ)

RPZ is a DNS firewall - block malicious domains, redirect, or return NXDOMAIN.

A.13.1. Enable RPZ in BIND

options {
    // ... other options ...
    response-policy {
        zone "rpz.local" policy given;        // Use zone-defined actions
        zone "rpz.blocklist" policy nxdomain; // All matches return NXDOMAIN
    };
};

zone "rpz.local" {
    type master;
    file "/var/named/rpz/rpz.local.zone";
    allow-query { none; };  // RPZ zones shouldn't be queried directly
};

A.13.2. RPZ Zone File Examples

; /var/named/rpz/rpz.local.zone
$TTL 3600
@   IN  SOA localhost. hostmaster.localhost. (
            2026021501 3600 900 604800 86400 )
    IN  NS  localhost.

; Block entire domain (returns NXDOMAIN)
malware.com             IN  CNAME  .

; Block specific host
evil.badsite.com        IN  CNAME  .

; Redirect to sinkhole (for logging/analysis)
phishing.com            IN  A      10.50.1.99

; Block by IP (any domain resolving to this IP)
32.1.2.3.rpz-ip         IN  CNAME  .

; Wildcard block (all subdomains)
*.malware-domain.com    IN  CNAME  .

; Passthru (whitelist - skip RPZ for this domain)
legitimate.com          IN  CNAME  rpz-passthru.

A.13.3. RPZ Actions

Action Effect

CNAME .

Return NXDOMAIN (domain doesn’t exist)

CNAME *.

Return NODATA (domain exists, no records)

CNAME rpz-passthru.

Allow query (whitelist)

CNAME rpz-drop.

Drop query silently (no response)

A 10.50.1.99

Redirect to sinkhole IP

A.13.4. Subscribe to Threat Feeds

# Download blocklist and convert to RPZ format
curl -s https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts | \
    grep "^0.0.0.0" | \
    awk '{print $2 " IN CNAME ."}' > /var/named/rpz/blocklist.zone

# Reload RPZ zone
sudo rndc reload rpz.local

A.14. ACL Definitions

Named ACLs make policies readable and maintainable.

A.14.1. Common ACL Patterns

// Localhost
acl "localhost" {
    127.0.0.1;
    ::1;
};

// Internal networks
acl "internal" {
    10.50.0.0/16;      // Management VLAN
    192.168.0.0/16;    // User VLANs
    172.16.0.0/12;     // Lab networks
};

// DNS slaves (for zone transfers)
acl "slaves" {
    10.50.1.91;        // bind-02
};

// Trusted resolvers (can recurse)
acl "trusted" {
    localhost;
    internal;
};

// Management hosts (can query version, stats)
acl "management" {
    10.50.1.10;        // Monitoring server
    10.50.1.5;         // Admin workstation
};

A.14.2. Using ACLs in Options

options {
    allow-query { trusted; };
    allow-recursion { trusted; };
    allow-transfer { slaves; };

    // Only management can query version/stats
    allow-query-cache { management; };
};

A.15. Practical Record Addition Examples

Copy-paste examples for adding records to your zone file.

A.15.1. Add A Record

# Edit zone file
sudo vi /var/named/inside.domusdigitalis.dev.zone

# Add line (remember to increment serial!)
newserver       IN  A       10.50.1.150

# Reload zone
sudo rndc reload inside.domusdigitalis.dev

# Verify
dig @localhost newserver.inside.domusdigitalis.dev +short

A.15.2. Add CNAME (Alias)

# In zone file (increment serial first!)
wiki            IN  CNAME   docs-01.inside.domusdigitalis.dev.

# Reload and verify
sudo rndc reload inside.domusdigitalis.dev
dig @localhost wiki.inside.domusdigitalis.dev +short

A.15.3. Add MX Record

# In zone file
@               IN  MX  10  mail-01.inside.domusdigitalis.dev.
@               IN  MX  20  mail-02.inside.domusdigitalis.dev.

# Verify
dig @localhost inside.domusdigitalis.dev MX +short

A.15.4. Add TXT Record (SPF)

# In zone file
@               IN  TXT     "v=spf1 mx ip4:10.50.1.0/24 -all"

# Verify
dig @localhost inside.domusdigitalis.dev TXT +short

A.15.5. Add SRV Record

# In zone file (LDAP service)
_ldap._tcp      IN  SRV     0 100 389 ipa-01.inside.domusdigitalis.dev.

# Verify
dig @localhost _ldap._tcp.inside.domusdigitalis.dev SRV +short

A.15.6. Add PTR Record (Reverse)

# Edit reverse zone
sudo vi /var/named/10.50.1.rev

# Add line (increment serial!)
150             IN  PTR     newserver.inside.domusdigitalis.dev.

# Reload and verify
sudo rndc reload 1.50.10.in-addr.arpa
dig @localhost -x 10.50.1.150 +short

A.15.7. Add CAA Record

# In zone file - only Let's Encrypt can issue certs
@               IN  CAA     0 issue "letsencrypt.org"
@               IN  CAA     0 issuewild "letsencrypt.org"
@               IN  CAA     0 iodef "mailto:security@domusdigitalis.dev"

# Verify
dig @localhost inside.domusdigitalis.dev CAA +short

A.15.8. Add SSHFP Record

# Generate SSHFP records from host keys
ssh-keygen -r newserver.inside.domusdigitalis.dev

# Example output to add to zone:
newserver       IN  SSHFP   1 1 abc123...
newserver       IN  SSHFP   1 2 def456...
newserver       IN  SSHFP   4 1 ghi789...
newserver       IN  SSHFP   4 2 jkl012...

# Client verification (requires DNSSEC or trusted resolver)
ssh -o VerifyHostKeyDNS=yes newserver.inside.domusdigitalis.dev

A.16. Quick Reference: Zone File Templates

A.16.1. Forward Zone Template

$TTL 3600
@   IN  SOA ns1.domain.com. hostmaster.domain.com. (
            2026021501 3600 900 604800 86400 )
    IN  NS      ns1.domain.com.
    IN  NS      ns2.domain.com.
    IN  MX  10  mail.domain.com.
    IN  TXT     "v=spf1 mx -all"

ns1     IN  A       10.0.0.1
ns2     IN  A       10.0.0.2
mail    IN  A       10.0.0.10
www     IN  CNAME   web.domain.com.
web     IN  A       10.0.0.20

A.16.2. Reverse Zone Template

$TTL 3600
@   IN  SOA ns1.domain.com. hostmaster.domain.com. (
            2026021501 3600 900 604800 86400 )
    IN  NS      ns1.domain.com.
    IN  NS      ns2.domain.com.

1       IN  PTR     ns1.domain.com.
2       IN  PTR     ns2.domain.com.
10      IN  PTR     mail.domain.com.
20      IN  PTR     web.domain.com.

A.17. Incident Response: DNS Indicators

A.17.1. Signs of Compromise

Indicator Investigation

Unexpected NS records

dig NS domain.com - compare to known good

TTL anomalies

Very low TTL (< 60s) may indicate fast-flux

New/unknown resolvers

Check /etc/resolv.conf, DHCP settings

NXDOMAIN for valid hosts

Cache poisoning or hijacking

High query rate to single domain

C2 beaconing or tunneling

A.17.2. Rapid Response Commands

# Dump current cache (what has BIND resolved recently)
sudo rndc dumpdb -cache
sudo cat /var/named/data/cache_dump.db

# Flush entire cache
sudo rndc flush

# Flush specific domain
sudo rndc flushname compromised.domain.com

# Reload zone without restarting
sudo rndc reload inside.domusdigitalis.dev

# Stop all queries (emergency)
sudo systemctl stop named

A.18. Performance Tuning

A.18.1. Key Metrics

# Query statistics
sudo rndc stats
cat /var/named/data/named_stats.txt

# Current connections
sudo ss -tunlp | grep named

# Memory usage
sudo rndc status | grep -E "heap|zone"

A.18.2. Tuning Parameters

options {
    // Worker threads (default: auto based on CPU)
    // recursive-clients 1000;    // Max concurrent recursive queries
    // tcp-clients 150;           // Max concurrent TCP connections

    // Cache tuning
    // max-cache-size 256M;       // Limit cache memory
    // max-cache-ttl 86400;       // Max TTL for cached records
    // max-ncache-ttl 3600;       // Max TTL for negative cache

    // Rate limiting (DDoS mitigation)
    rate-limit {
        responses-per-second 10;
        window 5;
    };
};

A.19. Further Reading

A.19.3. RFCs - Core DNS

RFC Description

RFC 1034

Domain Names - Concepts and Facilities

RFC 1035

Domain Names - Implementation and Specification

RFC 2181

Clarifications to the DNS Specification

RFC 2308

Negative Caching of DNS Queries (NXDOMAIN)

RFC 3596

DNS Extensions for IPv6 (AAAA records)

A.19.4. RFCs - Record Types

RFC Description

RFC 2782

SRV Records (Service Location)

RFC 4408

SPF Records (Sender Policy Framework)

RFC 6376

DKIM Signatures

RFC 7208

SPF (Updated)

RFC 7489

DMARC (Domain-based Message Authentication)

RFC 6844

CAA Records (Certificate Authority Authorization)

RFC 6698

TLSA Records (DANE)

RFC 4255

SSHFP Records (SSH Fingerprints)

A.19.5. RFCs - DNSSEC

RFC Description

RFC 4033

DNSSEC Introduction and Requirements

RFC 4034

Resource Records for DNSSEC

RFC 4035

Protocol Modifications for DNSSEC

RFC 5155

NSEC3 (Hashed Authenticated Denial of Existence)

RFC 6781

DNSSEC Operational Practices

RFC 7344

Automating DNSSEC Delegation Trust Maintenance

A.19.6. RFCs - Security & Operations

RFC Description

RFC 2845

TSIG (Transaction Signatures for DNS)

RFC 5936

AXFR (Zone Transfer)

RFC 1996

NOTIFY (Zone Change Notification)

RFC 7766

DNS Transport over TCP

RFC 7816

Query Name Minimization

RFC 8020

NXDOMAIN Cut (Aggressive Negative Caching)

A.19.7. RFCs - Encrypted DNS

RFC Description

RFC 7858

DNS over TLS (DoT)

RFC 8484

DNS over HTTPS (DoH)

RFC 9250

DNS over QUIC (DoQ)

RFC 8310

Usage Profiles for DNS over TLS/DTLS

A.19.8. RFCs - Response Policy Zones (RPZ)

RFC Description

RPZ Draft

DNS Response Policy Zones (Internet Draft)

RPZ.info

Community RPZ Documentation

A.19.9. Books & Training

A.19.10. Threat Intelligence Feeds (RPZ Sources)