Replace PowerDNS by Knot DNS and Knot Resolver+supervisor with DNSSEC, DNS over TLS and domain name spoofing

I was considering using Knot DNS since a while. Switching to DNS over TLS for the resolver queries was the push needed. Turns out that transposing my setup with Knot DNS is very easy and fast.

Knot Resolver does not provide init scripts and suggests to use supervisor as an alternative to systemd omnipresent features. Wary with this idea, turns out that supervisor is very easy to put in place and I might use it more, replace some xinetd, in the future.

For the record, my setup is as follow: there is a local DNS server to serve the local area network HERE.ici domain and is a resolver that cache requests. All requests are sent to the resolver and this one, if he cannot answer, then ask the relevant DNS server. Nothing too fancy, even if sometimes LAN are set up the other way around, where people query the local DNS server by default and this one query the local resolver if he can’t answer.

Install require the following:

apt install knot/testing
apt install knot-resolver supervisor

DNS for the local area network

The Knot DNS server will not be queried directly but by the Knot Resolver and DHCPd. Edit /etc/knot/knot.conf by adding:

server:
    # meant to be called only on loopback
    # by knot-resolver and dhcpd on update
    listen: 127.0.1.1@53

acl:
  - id: update_acl
    # restrict by IP is enough, no need for a ddns key stored on the same host
    address: 127.0.0.1
    action: update

zone:
  - domain: HERE.ici
    dnssec-signing: on
    acl: update_acl

  - domain: 10.in-addr.arpa
    dnssec-signing: on
    acl: update_acl

Create the zones (edit serverhostname and HERE.ici according to your setup):

invoke-rc.d knot restart

knotc zone-begin HERE.ici
knotc zone-set HERE.ici @ 7200 SOA serverhostname hostmaster 1 86400 900 691200 3600
knotc zone-set HERE.ici serverhostname 3600 A 10.10.10.1
knotc zone-set HERE.ici @ 3600 NS serverhostname
knotc zone-set HERE.ici @ 3600 MX 10 mx.HERE.ici
knotc zone-set HERE.ici jeden 3600 CNAME serverhostname
knotc zone-commit HERE.ici

knotc zone-begin 10.in-addr.arpa
knotc zone-set 10.in-addr.arpa @ 7200 SOA serverhostname.HERE.ici. hostmaster.HERE.ici. 1 86400 900 691
200 3600
knotc zone-set 10.in-addr.arpa 10.10.10.1 3600 PTR serverhostname
knotc zone-set 10.in-addr.arpa @ 3600 NS serverhostname.HERE.ici.
knotc zone-commit 10.in-addr.arpa

Zone will to be updated by the DHCP server, in this case ISC dhcpd. Edit /etc/dhcp/dhcpd.conf accordingly:

# dynamic update
ddns-updates on;
ddns-update-style standard;
ignore client-updates; # restrict to domain name

# option definitions common to all supported networks...
option domain-name "HERE.ici";
option domain-search "HERE.ici";
# you can add other extra name servers if you consider acceptable 
# direct external queries in case the resolver is dead
option domain-name-servers 10.0.0.1;
option routers 10.0.0.1;
default-lease-time 600;
max-lease-time 6000;
update-static-leases on;
authoritative;

 [...]

zone HERE.ici. {
  primary 127.0.1.1;
}
zone 10.in-addr.arpa. {
  primary 127.0.1.1;
}

No dynamic update keys, everything goes through the loopback. You might want erase DHCPd leases (usually in /var/lib/dhcp/) so it does not get confused.

DNS Resolver

The Knot Resolver will handle all clients queries, contacting Internet DNS over TLS if need be and caching results. Edit /etc/knot-resolver/kresd.conf to contain:

-- Network interface configuration
-- (knot dns should be using 127.0.1.1)
net.listen('127.0.0.1', 53, { kind = 'dns' })
net.listen('127.0.0.1', 853, { kind = 'tls' })
net.listen('10.0.0.1', 53, { kind = 'dns' })
net.listen('10.0.0.1', 853, { kind = 'tls' })

-- drop privileges (check /var/lib/knot-resolves modes/owner)
user('knot-resolver', 'knot-resolver')

-- Load useful modules
modules = {
   'hints > iterate',  -- Load /etc/hosts and allow custom root hints
   'stats',            -- Track internal statistics
   'predict',          -- Prefetch expiring/frequent records
   'view', 	       -- require to limit access
}

-- Cache size
cache.size = 500 * MB

-- whitelist queries identified by subnet
view:addr('127.0.0.0/24', policy.all(policy.PASS))
view:addr('10.0.0.0/24', policy.all(policy.PASS))
-- drop everything that hasn't matched
view:addr('0.0.0.0/0', policy.all(policy.DROP))

-- Custom hints: local spoofed address and antispam/ads
hints.add_hosts("/etc/knot-resolver/redirect-spoof")
hints.add_hosts("/etc/knot-resolver/redirect-ads")

-- internal domain: use knot dns listening on loopback
internalDomains = policy.todnames({'HERE.ici', '10.in-addr.arpa'})
policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), internalDomains))
policy.add(policy.suffix(policy.STUB({'127.0.1.1@53'}), internalDomains))

-- forward in TLS					
policy.add(policy.all(policy.TLS_FORWARD(
			 {'208.67.222.222', hostname='dns.opendns.com'},
			 {'208.67.220.220', hostname='dns.opendns.com'},
 			 {'1.1.1.1', hostname='cloudflare-dns.com'},
			 {'1.0.0.1', hostname='cloudflare-dns.com'},
})))

redirect-spoof and redirect-ads at /etc/hosts format: it allows domain spoofing or ads domains filtering. It replaces conveniently the extra lua script that my setup was using with PowerDNS.

Update Feb 19 2023: Check recent files on gitlab, I know use RPZ instead of hints/hosts file to block hostile domains. No real change in principle but knot-resolver seems to handle better very long lists in this form.

Finally, the resolver need to be started by the supervisord, with a /etc/supervisor/conf.d/knot-resolver.conf as such:

[program:knot-resolver]
command=/usr/sbin/kresd -c /etc/knot-resolver/kresd.conf --noninteractive
priority=0
autostart=true
autorestart=true
stdout_syslog=true
stderr_syslog=true
directory=/var/lib/knot-resolver

[program:knot-resolver-gc]
command=/usr/sbin/kres-cache-gc -c /var/lib/knot-resolver -d 120000
user=knot-resolver
autostart=true
autorestart=true
stdout_syslog=true
stderr_syslog=true
directory=/var/lib/knot-resolver

Restart the supervisor, check logs. Everything should be fine. You can cleanup.

rc-update add supervisor
rc-update add knot
apt --purge remove pdns-*

# check if there is still traffic on DNS port 53 on the public network interface (should be none)
tcpdump -ni eth0 -p port  53
# check if there is trafic on DNS over TLS port 853 (should be whenever there is a query outside of the cache and LAN)
tcpdump -ni eth0 -p port  853

(My default files are my rien-host package; if you have on your network a mail server using DNS blacklist which will inevitably blocked, you might want to install knot-resolver also on this server, in recursive mode)