Extended Brain Storage

OpenBSD: Validating and Caching DNS Resolver

Posted on September 18, 2016

It is strongly recommended to handle DNS requests mindfully in order to deal with various types of attacks. OpenBSD comes with Unbound -- a DNS caching resolver...

General Theory

The Domain Name System (DNS) is a hierarchical decentralized naming system which is defined in two main documents:

Since its original design in 1983, it has evolved quite a lot. Traditionally, DNS responses are not cryptographically signed, which can lead to many attack possibilities. Therefore, various methods and protocols have been proposed:

DNSSEC was designed to protect Internet resolvers (clients) from forged DNS data (e.g. by DNS cache poisoning). It is a set of DNS extensions (RFC6891), which provide:

DNSSEC is not an almighty solution and has its weaknesses (complexity, increased size of response packets and resolver's load, etc.)

DNSCrypt helps mitigate DNS spoofing, contributes to online privacy and ad blocking, and can be used either over UDP or over TCP. In both cases, its default port is 443, even though the protocol radically differs from HTTPS. Instead of relying on trusted certificate authorities (CA) commonly found in web browsers, the client has to explicitly trust the public signing key of the chosen upstream forwarder.

Note: It is apparent, that neither DNSSEC, DoT, DoH, DNSCurve or DNSCRYPT is the silver bullet. There are even reasonable votes against. Reader discretion advised!


General Resolver Setup

The DNS servers are specified in the resolv.conf file as follows:

$ grep nameserver /etc/resolv.conf
nameserver IP_ADDRESS1
nameserver IP_ADDRESS2

These records can be assigned dynamically (e.g. using DHCP client) or defined manually assuming that the administrator trusts its upstream DNS forwarder/recursive resolver (e.g. DNS server provided by its ISP).


Caching DNS Resolver

Unbound can be used to store DNS responses in its cache, and thus; act as a local caching resolver (nameserver). Considering IPv4 environment only, Unbound can be configured in the unbound.conf file as follows:

$ grep -v -E "^#|^.#|^$" /var/unbound/etc/unbound.conf
server:
        interface: 127.0.0.1
        #interface: ::1
        do-ip6: no
        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: ::0/0 refuse
        access-control: ::1 refuse
        hide-identity: yes
        hide-version: yes
        do-not-query-localhost: no
remote-control:
        control-enable: yes
        control-use-cert: no
        control-interface: /var/run/unbound.sock
forward-zone:
        name: "."
        forward-addr: IP-ADDRESS-OF-TRUSTED-DNS-FORWARDER

Obviously, at least one IP-ADDRESS-OF-TRUSTED-DNS-FORWARDER needs to be specified (e.g. the one provided by the ISP).

Before starting Unbound, its configuration can be checked for validity as follows:

$ unbound-checkconf

Having all set up, the caching DNS resolver needs to be enabled and started:

$ rcctl {enable|start} unbound
unbound(ok)

and checked:

$ rcctl check unbound 
unbound(ok)

The setup can be tested as follows (expecting IPv4 or IPv6 address in response):

$ dig @127.0.0.1 www.rohlix.eu +short
46.28.109.174

Note: All the steps, that were necessary in order to implement the DNS caching resolver using Unbound, have been completed. As discussed in the introduction section, the following steps are additional ways of how to somewhat improve the plain old DNS.


Manual Filtering of Undesirable Domain Names

The advertisements and spams have become more and more pervasive and irritating. The most effective way to get rid of most of them is to block the DNS translation by "spoofing" the replies using a validating and caching DNS resolver (e.g. Unbound). All undesirable domain names will be translated to 127.0.0.1 (IPv4 localhost).

The Unbound's configuration (server section) needs to be updated with the following (include directive):

$ vi /var/unbound/etc/unbound.conf
server:
...
        # Activate advertisements and spam filtering
        include: "/var/unbound/etc/unbound-ad-filtering.conf"
remote-control:
...

If trusted, the anti-ad server list can be downloaded (e.g. from pgl.yoyo.org) as follows:

$ curl -sS -L --compressed "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext" > /var/unbound/etc/unbound-ad-filtering.conf

And finally, Unbound to be restarted:

$ rcctl restart unbound

Enabling DNS over TLS (DoT)

Configuration of Unbound:

$ vi /var/unbound/etc/unbound.conf
...
forward-zone:
...
	# DNS over TLS support
	forward-ssl-upstream: yes
	forward-addr: 1.1.1.1@853               # Cloudflare DNS over TLS
#	forward-addr: 1.0.0.1@853
	forward-addr: 9.9.9.9@853               # Quad9 (PCH/IBM) DNS over TLS
#	forward-addr: 149.112.112.112@853
#	forward-addr: 2606:4700:4700::1111@853  # IPv6 Cloudflare DNS over TLS
#	forward-addr: 2606:4700:4700::1001@853

And finally, Unbound to be restarted:

$ rcctl restart unbound

Enabling DNS over HTTPS (DoH)

To be discussed later.


Enabling DNSSEC

In order to enable DNSSEC support, the auto-trust-anchor-file directive in the unbound.conf file needs to be enabled as follows:

$ vi /var/unbound/etc/unbound.conf
auto-trust-anchor-file: "/var/unbound/db/root.key"

The Root Zone trust anchor needs to be downloaded and verified from IANA:

$ cd /var/unbound/db/
$ ftp https://data.iana.org/root-anchors/root-anchors.xml
$ ftp https://data.iana.org/root-anchors/root-anchors.p7s
$ openssl smime -verify -noverify -inform DER -in root-anchors.p7s -content root-anchors.xml

From now on, the root.key will be automatically updated by Unbound.

It is also necessary to mention that the forward-addr directive in unbound.conf needs to point to a DNSSEC enabled upstream forwarder. If none available, the file contains predefined servers such as Google's, OpenDNS's, etc.

Having all set up, the validating and caching DNS resolver needs to be restarted:

$ rcctl restart unbound
unbound(ok)
unbound(ok)

and checked:

$ rcctl check unbound 
unbound(ok)

The setup can be tested as follows (expecting IPv4 or IPv6 address including its signature in response):

$ dig @127.0.0.1 www.rohlix.eu +short +dnssec
46.28.109.174
A 7 3 1800 20180328072024 20180216055710 14060 rohlix.eu. foLvijIKqPFJPX0QMKM7puiFf8hnAByxCLEw3tEFRUlHCTHuAB7vLLtJ SMRnJVylFCvUbeYbK5R6n17cAIaZvq+FQGEkMEBsHdwLDhBHc0G49o9D N6ihqULe4vgBLAcwXZpcpKXbwnSpa6DrheeMfakYRYNX3Mwk1yuZ0TwS ACk=

Enabling DNSCrypt

DNSCrypt was never proposed to the Internet Engineering Task Force (IETF) by the way of a Request for Comments (RFC) and is currently maintained by a private company. Reader discretion advised!

In OpenBSD, the DNSCrypt protocol is implemented using DNSCrypt proxy, which can be installed as follows:

$ pkg_add dnscrypt-proxy
quirks-2.367 signed on 2017-10-03T11:21:28Z
dnscrypt-proxy-1.9.5:libexecinfo-0.3p0v0: ok
dnscrypt-proxy-1.9.5:libsodium-1.0.13p0: ok
dnscrypt-proxy-1.9.5: ok
The following new rcscripts were installed: /etc/rc.d/dnscrypt_proxy
See rcctl(8) for details.
Look in /usr/local/share/doc/pkg-readmes for extra documentation.

The list of available DNS servers can be found in:

$ less /usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv

If the privacy is of any concern no US/RUS/etc. resolver should be ever used. Considering the EU, the dnscrypt.eu project seems as a reasonable solution (more info:

$ grep DNSCrypt.eu /usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv 
dnscrypt.eu-dk,"DNSCrypt.eu Denmark","Free, non-logged, uncensored. Hosted by Netgroup.","Denmark","",https://dnscrypt.eu,1,yes,yes,no,77.66.84.233,2.dnscrypt-cert.resolver2.dnscrypt.eu,3748:5585:E3B9:D088:FD25:AD36:B037:01F5:520C:D648:9E9A:DD52:1457:4955:9F0A:9955,pubkey.resolver2.dnscrypt.eu
dnscrypt.eu-dk-ipv6,"DNSCrypt.eu Denmark over IPv6","Free, non-logged, uncensored. Hosted by Netgroup.","Denmark","",https://dnscrypt.eu,1,yes,yes,no,[2001:1448:243::dc2]:443,2.dnscrypt-cert.resolver2.dnscrypt.eu,3748:5585:E3B9:D088:FD25:AD36:B037:01F5:520C:D648:9E9A:DD52:1457:4955:9F0A:9955,pubkey.resolver2.dnscrypt.eu
dnscrypt.eu-nl,"DNSCrypt.eu Holland","Free, non-logged, uncensored. Hosted by RamNode.","Netherlands","",https://dnscrypt.eu,1,yes,yes,no,176.56.237.171,2.dnscrypt-cert.resolver1.dnscrypt.eu,67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66,pubkey.resolver1.dnscrypt.eu
dnscrypt.eu-nl-ipv6,"DNSCrypt.eu Holland over IPv6","Free, non-logged, uncensored. Hosted by RamNode.","Netherlands","",https://dnscrypt.eu,1,yes,yes,no,[2a00:d880:3:1::a6c1:2e89]:443,2.dnscrypt-cert.resolver1.dnscrypt.eu,67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66,pubkey.resolver1.dnscrypt.eu

The CSV is actually not a CSV, as commas are not used properly, hence the following sed scripts:

$ grep DNSCrypt.eu /usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv \
   | sed "s/, /~/g" | sed "s/,/;/g" | sed "s/~/, /g"
dnscrypt.eu-dk;"DNSCrypt.eu Denmark";"Free, non-logged, uncensored. Hosted by Netgroup.";"Denmark";"";https://dnscrypt.eu;1;yes;yes;no;77.66.84.233;2.dnscrypt-cert.resolver2.dnscrypt.eu;3748:5585:E3B9:D088:FD25:AD36:B037:01F5:520C:D648:9E9A:DD52:1457:4955:9F0A:9955;pubkey.resolver2.dnscrypt.eu
dnscrypt.eu-dk-ipv6;"DNSCrypt.eu Denmark over IPv6";"Free, non-logged, uncensored. Hosted by Netgroup.";"Denmark";"";https://dnscrypt.eu;1;yes;yes;no;[2001:1448:243::dc2]:443;2.dnscrypt-cert.resolver2.dnscrypt.eu;3748:5585:E3B9:D088:FD25:AD36:B037:01F5:520C:D648:9E9A:DD52:1457:4955:9F0A:9955;pubkey.resolver2.dnscrypt.eu
dnscrypt.eu-nl;"DNSCrypt.eu Holland";"Free, non-logged, uncensored. Hosted by RamNode.";"Netherlands";"";https://dnscrypt.eu;1;yes;yes;no;176.56.237.171;2.dnscrypt-cert.resolver1.dnscrypt.eu;67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66;pubkey.resolver1.dnscrypt.eu
dnscrypt.eu-nl-ipv6;"DNSCrypt.eu Holland over IPv6";"Free, non-logged, uncensored. Hosted by RamNode.";"Netherlands";"";https://dnscrypt.eu;1;yes;yes;no;[2a00:d880:3:1::a6c1:2e89]:443;2.dnscrypt-cert.resolver1.dnscrypt.eu;67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66;pubkey.resolver1.dnscrypt.eu

In order to get the necessary details from the CSV, the following columns need to be considered:

$1 ~ Name
$8 ~ DNSSEC validation
$11 ~ Resolver address
$12 ~ Provider name
$13 ~ Provider public key
$14 ~ Provider public key TXT record

Selecting "uncensored" and "non-ipv6" servers and printing the previously described columns can be done as follows:

$ grep -i uncensored /usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv \
  | grep -v "ipv6" | sed "s/, /~/g" | sed "s/,/;/g" | sed "s/~/, /g" | awk -F';' '{print $1"\t"$8"\t"$11"\t"$12"\t"$13}' | grep yes
dnscrypt.eu-dk  yes     77.66.84.233    2.dnscrypt-cert.resolver2.dnscrypt.eu   3748:5585:E3B9:D088:FD25:AD36:B037:01F5:520C:D648:9E9A:DD52:1457:4955:9F0A:9955
dnscrypt.eu-nl  yes     176.56.237.171  2.dnscrypt-cert.resolver1.dnscrypt.eu   67C0:0F2C:21C5:5481:45DD:7CB4:6A27:1AF2:EB96:9931:40A3:09B6:2B8D:1653:1185:9C66
dnscrypt.org-fr yes     212.47.228.136  2.dnscrypt-cert.fr.dnscrypt.org E801:B84E:A606:BFB0:BAC0:CE43:445B:B15E:BA64:B02F:A3C4:AA31:AE10:636A:0790:324D
ns0.dnscrypt.is yes     93.95.228.87    2.dnscrypt-cert.ns0.dnscrypt.is EE41:6A83:451C:218F:37B2:B736:78C4:999F:7DE6:89D1:31D2:7866:7C8E:A8BB:1C95:B402
nxd.ist yes     104.196.239.247 2.dnscrypt-cert.nxd.ist 146D:E394:BDCD:25F5:AA68:822A:A9D7:4792:C07E:5DF2:7172:3CBD:2347:161A:4433:8F59

Assuming the dnscrypt.org-fr (dnscrypt.eu did not work at the time of testing), the DNSCrypt proxy can be tested by running the following command:

$ dnscrypt-proxy --local-address=127.0.0.1:5353 \
--resolvers-list=/usr/local/share/dnscrypt-proxy/dnscrypt-resolvers.csv \
--resolver-address=212.47.228.136 \
--provider-name=2.dnscrypt-cert.fr.dnscrypt.org \
--provider-key=E801:B84E:A606:BFB0:BAC0:CE43:445B:B15E:BA64:B02F:A3C4:AA31:AE10:636A:0790:324D \
--loglevel=7 --ephemeral-keys --tcp-only

Based on the previous selection and its successful test, the DNSCrypt proxy can be enabled (locally, i.e. on 127.0.0.1, listening on TCP port 5353) as follows:

$ rcctl enable dnscrypt_proxy
$ rcctl set dnscrypt_proxy flags "--local-address=127.0.0.1:5353 --provider-key=E801:B84E:A606:BFB0:BAC0:CE43:445B:B15E:BA64:B02F:A3C4:AA31:AE10:636A:0790:324D --provider-name=2.dnscrypt-cert.fr.dnscrypt.org --resolver-address=212.47.228.136 --tcp-only --ephemeral-keys -l /dev/null"
$ rcctl start dnscrypt_proxy

The last thing is to update Unbound's forward-addr directive to query the DNSCrypt proxy (on 127.0.0.1@5353) as follows:

$ vi /var/unbound/etc/unbound.conf
forward-zone:
    name = "."
    forward-addr: 127.0.0.1@5353

In order to apply the changes, Unbound needs to be restarted:

$ rcctl restart unbound

In order to ensure that WAN DHCP offers do not overwrite the above setup in /etc/resolv.conf, the following step needs to be performed:

$ echo "supersede domain-name-servers 127.0.0.1;" >> /etc/dhclient.conf

Tags: #OpenBSD #security #validating #caching #DNS #resolver #DNSSEC #DNSCRYPT

⏴ Previous Post Next Post ⏵