This tutorial shows how to set up a secure DNS server in your home network, enable DNS-over-TLS and DNSSEC to protect your DNS privacy. I will also show how to test and examine the setup to make sure everything is configured correctly.
This is what you need before you begin:
This tutorial assumes that Raspbian Buster Lite (September 2019) and Pi-hole (v4.3.2) are up and running.
Installing unbound
sudo apt install -y unbound
sudo systemctl enable unbound
Basic resolver
This is the minimal configuration you can have. It will just forward requests and cache DNS responses.
/etc/unbound/unbound.conf.d/pihole.conf
server:
port: 5300
forward-zone:
name: "."
forward-addr: 46.182.19.48 # digitalcourage.de
Check config-file for errors with unbound-checkconf
henry@pizero:[~]: unbound-checkconf
unbound-checkconf: no errors in /etc/unbound/unbound.conf
Restart unbound: sudo systemctl restart unbound
Test resolver: dig @::1 -p 5300 mozilla.org
henry@pizero:[~]: dig @::1 -p 5300 mozilla.org
; <<>> DiG 9.11.5-P4-5.1-Raspbian <<>> @::1 -p 5300 mozilla.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 618
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;mozilla.org. IN A
;; ANSWER SECTION:
mozilla.org. 28 IN A 63.245.208.195
;; Query time: 317 msec
;; SERVER: ::1#5300(::1)
;; WHEN: Wed Jan 15 18:14:19 GMT 2020
;; MSG SIZE rcvd: 56
Check DSN-Traffic with tcpdump and wireshark:
First, restart unbound to clear the cache: sudo systemctl restart unbound
Open another terminal and run tcpdump:
henry@pizero:[~]: sudo tcpdump port 53 -w basic_dns.pcap
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
Re-run the DNS query in the first terminal dig @::1 -p 5300 mozilla.org
. When it’s done, exit tcpdump with CTRL-C
:
14 packets captured
14 packets received by filter
0 packets dropped by kernel
Opening the trace with wireshark reveals the DNS traffic which is currently unencrypted:
DNS-over-TLS
Update the certificates with: sudo update-ca-certificates
Modify the configuration file /etc/unbound/unbound.conf
as follows:
server:
port: 5300
tls-upstream: yes
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
forward-zone:
name: "."
forward-addr: 2a05:fc84::42@853#dns.digitale-gesellschaft.ch
Test with dig @::1 -p 5300 mozilla.org
henry@pizero:[~]: dig @::1 -p 5300 mozilla.org
; <<>> DiG 9.11.5-P4-5.1-Raspbian <<>> @::1 -p 5300 mozilla.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31328
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;mozilla.org. IN A
;; ANSWER SECTION:
mozilla.org. 60 IN A 63.245.208.195
;; Query time: 1019 msec
;; SERVER: ::1#5300(::1)
;; WHEN: Sun Jan 12 07:49:16 GMT 2020
;; MSG SIZE rcvd: 56
Check DNS-Traffic
Make a DNS request dig @::1 -p 5300 mozilla.org
while capturing the traffic with sudo tcpdump host 2a05:fc84::42 -w tls.pcap
All DNS traffic is now wrapped in a TLS connection.
DNSSEC
To protect the DNS-responses against modification, we will use DNSSEC. Unbound checks DNS responses against known public keys. These keys MUST be updated initially and kept up to date regularly. The initial update must be done manually, whereas unbound updates them regularly while running.
Make sure that the key-file ist part of your unbound-configuration:
henry@pizero:[~]: cat /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf
server:
# The following line will configure unbound to perform cryptographic
# DNSSEC validation using the root trust anchor.
auto-trust-anchor-file: "/var/lib/unbound/root.key"
Update the keys sudo -u unbound unbound-anchor
and restart unbound sudo systemctl restart unbound
.
Testing with a valid DNSSEC enabled domain:
henry@pizero:[~]: dig @::1 -p 5300 mozilla.org +dnssec
; <<>> DiG 9.11.5-P4-5.1-Raspbian <<>> @::1 -p 5300 mozilla.org +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20519
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;mozilla.org. IN A
;; ANSWER SECTION:
mozilla.org. 44 IN A 63.245.208.195
mozilla.org. 44 IN RRSIG A 7 2 60 20200115032436 20200112022436 45629 mozilla.org. DlXn7EgUdqlwyMtSDExb4t+d1534Aef9jMkZZFXVERjths9Lhe2i9eWh tgbmz9AS6eGh8K1M0ZMaEztzsBaDn8e6JsrCAnGk+rU51iwAwk5pJVFP ERMk+Sq1fGoeKaWNlImD4S3sidr3N/zRDdh76s9zgBtIHY8RCqagRBP7 L1k=
;; Query time: 162 msec
;; SERVER: ::1#5300(::1)
;; WHEN: Sun Jan 12 14:25:35 GMT 2020
;; MSG SIZE rcvd: 227
Note the NOERROR-status and ad-flag.
Negative-test:
henry@pizero:[~]: dig @::1 -p 5300 dnssec-failed.org +dnssec
; <<>> DiG 9.11.5-P4-5.1-Raspbian <<>> @::1 -p 5300 dnssec-failed.org +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 24444
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;dnssec-failed.org. IN A
;; Query time: 2313 msec
;; SERVER: ::1#5300(::1)
;; WHEN: Sun Jan 12 14:27:36 GMT 2020
;; MSG SIZE rcvd: 46
Status is SERVFAIL
and no address was resolved.
Tweaks
The final config with 4 upstream resolvers incl. some minor tweaks for speed/privacy:
server:
# basic
port: 5300
# IPv4
interface: 0.0.0.0@5300
access-control: 192.168.178.0/24 allow
# IPv6
interface: ::0@5300
access-control: fe80::/10 allow
# TLS settings
tls-upstream: yes
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
# performance optimizations (costs more traffic and/or CPU)
prefetch: yes
prefetch-key: yes
rrset-roundrobin: yes
qname-minimisation-strict: yes
hide-identity: yes
hide-version: yes
# upstream resolver settings
forward-zone:
name: "."
forward-addr: 2a05:fc84::42@853#dns.digitale-gesellschaft.ch
forward-addr: 2a01:3a0:53:53::@853#unicast.censurfridns.dk
forward-addr: 2a02:2970:1002::18@853#dns2.digitalcourage.de
forward-addr: 2a03:b0c0:0:1010::e9a:3001@853#dot.securedns.eu
Pi-hole config
The only thing left is to set the local unbound-instance as upstream resolver:
Privacy
Encrypting DNS-Traffic with DNS-over-TLS can NOT completely protect your privacy. The DNS-requests and -responses will be encrypted and are authenticated with DNSSEC. But on connecting to the resolved domain via TLS the visited domain is readable in plaintext in the HTTPS-Traffic via the Server Name Indication (SNI)
extension:
Capture traffic in 1st terminal: sudo tcpdump "tcp port 443" -w https.pcap
henry@pizero:[~]: sudo tcpdump "tcp port 443" -w https.pcap
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
Create HTTPS request in another terminal: curl -I https://cyclemap.link
henry@pizero:[~]: curl -I https://cyclemap.link
HTTP/2 200
content-type: text/html
content-length: 33001
date: Sun, 12 Jan 2020 09:28:56 GMT
last-modified: Sat, 19 Oct 2019 08:33:39 GMT
etag: "a0a1914e588d03e34546d4ef595627b0"
accept-ranges: bytes
server: AmazonS3
vary: Accept-Encoding
x-cache: Hit from cloudfront
via: 1.1 c5c25772c7f14e267596e0f8ce51d9bc.cloudfront.net (CloudFront)
x-amz-cf-pop: FRA53-C1
x-amz-cf-id: HYQeKS19JhtyPhvI2b-z-kJBOxJaq-rFiIltrtorW8710w7EnYj-lQ==
age: 155
To prevent leaking this information we need to encrypt the SNI.
Debugging
Check unbound logs with:
sudo journalctl -u unbound -n 10 -f
Set-up the Raspberry Pi with Network Watchdog to prevent downtime due to network issues.
Persist logfiles over reboots: Beginners Guide to “journalctl”
References
Know-how
- Unbound documentation
- Actually secure DNS over TLS in Unbound
- Configure Pi Hole for DNS Over TLS
- How to test and validate DNSSEC using dig command line
- Tcpdump Examples
- RasPi: openVPN, piHole with DNSoverHTTPS and DNSoverTLS