Encrypted DNS with Caching using Unbound

Domain Name Service (DNS) is an important vulnerability for most systems, and particularly so for laptops, which are generally set up to be quite promiscuous when it comes to DNS.

The basic concern is that when you connect to a network that uses DHCP (most WiFi networks are this way), then you generally accept the network’s choice of name servers. They may offer a nefarious name server or they may record and track your requests. The name server converts domain names to IP addresses. In that way it is like a phone book. The problem is that a nefarious name server might convert the domain name of your bank to the IP address of a site that is looks just like your banks site but is designed to steal your username and password. To see a humorous and largely harmless version of this, see Upside-Down-Ternet.

Even without wi-fi, allowing your ISP to provide your DNS also allows them to track your online activity and build a profile of you that they can sell to online advertisers. Allowing your company to provide your DNS allows them to track or censor your activities.

This note tells you how to take control of your DNS and reduce your chances of succumbing to these particular attacks.

In order to harden DNS on my laptop, I installed and ran Unbound as a daemon and configured my laptop to always use it to provide DNS service. This protects me in three ways: first, Unbound assures that it talks only to the name servers I have chosen, second, it encrypts the connection so the responses cannot be observed or modified, and third, it uses DNSSEC to assure that the responses are correct. In addition, Unbound caches results to improve the responsiveness of your network activity.

I will describe how I did all of this on Fedora Linux.

Installing and Configuring Unbound

Install Unbound with:

dnf install unbound

This creates a starter configuration file in /etc/unbound/unbound.conf. It is separated into different sections. We will use the server and forward-zone sections. The server section is for global settings and the forward-zone section is for configuring the servers that Unbound should forward our requests to in its role as a validating caching name server.

The initial configuration can mostly be left unchanged. However, the following settings should be configured as shown to tailor Unbound to act as a caching resolver:

server:
    # provide unencrypted dns services on port 53
    interface: 127.0.0.1@53
    interface: ::1@53
    port: 53

    # provide TLS protected dns services on port 853
    # this is generally not needed for local use
    tls-service-key: "/etc/pki/tls/private/privkey.pem"
    tls-service-pem: "/etc/pki/tls/certs/fullchain.pem"
    interface: 127.0.0.1@853
    interface: ::1@853
    tls-port: 853

    # support both ipv6 and tcp
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    # only allow access from localhost
    access-control: 0.0.0.0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: ::0/0 refuse
    access-control: ::1 allow

    # enable dnssec
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # certificate authorities needed to authenticate upstream servers
    tls-cert-bundle: "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"

forward-zone:
    name: "."
    forward-tls-upstream: yes

    # Cloudflare DNS
    forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com
    forward-addr: 1.0.0.1@853#cloudflare-dns.com

    # NordVPN
    forward-addr: 103.86.96.100@853#dns1.nordvpn.com
    forward-addr: 103.86.99.100@853#dns2.nordvpn.com

    # Quad9
    forward-addr: 2620:fe::fe@853#dns.quad9.net
    forward-addr: 9.9.9.9@853#dns.quad9.net
    forward-addr: 2620:fe::9@853#dns.quad9.net
    forward-addr: 149.112.112.112@853#dns.quad9.net

To generate the trust-anchor file for DNSSEC you need to run unbound-anchor or you can get it from https://www.internic.net/domain/named.cache.

The “.” passed to name in forward-zone matches all names and so specifies that all requests should be sent to the configured resolvers. You can have multiple forward-zone sections, but then each should have different names.

The companies that provide the configured servers (Cloudflare, NordVPN, and Cloud9) all claim to be privacy oriented and so do not normally log your IP address. Unbound distributes its requests evenly to all configured servers, so the more servers that are configured the fewer of your requests any one actually sees, making it more difficult to for them to get a complete picture of your activities even if they tried.

If none of the configured resolvers respond, then as configured Unbound does not resolve the name if it is not in the cache. To get Unbound to try to resolve the name itself in this case, you can add:

forward-zone:
    forward-first: yes

Be aware that in this case the servers you contact might not be as privacy oriented as the configured servers.

This configuration is designed to only serve names locally. If you want to want to serve names to peers, you would need to open the desired ports (53 and perhaps 853) in your firewall and change access-control as follows:

# only allow access from localhost
access-control: 0.0.0.0/0 allow
access-control: 127.0.0.0/8 allow
access-control: ::0/0 allow
access-control: ::1 allow

Notice that in the above configuration TLS support was included even though the server was only intended to be used locally, and there is a comment that says that TLS is not really needed locally. This seeming contradiction can be understood by recognizing that an external resolver that is configured to only use TLS may use SSH to gain access to the server. From the perspective of access-control that looks like a local access, but the external resolver needs to use TLS (some caching resolvers, such as Unbound, either use TLS for all of its source resolvers or none of them). This situation is addressed more fully in the next section.

The upstream resolvers are identified with forward-addr. For example:

forward-addr: 1.1.1.1@853#cloudflare-dns.com

The first part, before the @, is the IP address. The middle part, between @ and #, is the port, and the trailing part, after the #, is the hostname. The host name should match the certificate being used by the server. This last part is optional, but not providing it allows you to connect to another DNS server as long as it has a valid certificate, which allows man-in-the-middle attacks.

To test that TLS is working, you test your local Unbound server using:

openssl s_client -connect localhost:853

You can find the name suitable for an upstream server using:

openssl s_client -connect 1.1.1.1:853

The host name you append to your forward-addr should match that given as the CN name reported by openssl.

Once configured, you can start Unbound using:

sudo systemctl start unbound

Test it with:

sudo systemctl status unbound
dig @localhost google.com

When all looks good, you can enable it so that it starts up automatically using:

sudo enable unbound

Finally, you can configure you computer to use Unbound as its name server using:

sudo nmcli con mod eth0 ipv4.dns '127.0.0.1'
sudo systemctl restart NetworkManager

where eth0 is the name of the network interface.

Laptop Configuration

In most cases the secure caching server configuration works well on a laptop. However, there is a particular situation where it fails; when the laptop is behind a corporate firewall that inserts itself as a man-in-the-middle in all connections. If your TLS CA bundle does not have the corporate certificates, Unbound refuses to connect to the external resolvers in this case. Of course this is expected and normally desirable as it protects you. However, this situation prevents you from resolving any names. In addition, in some extreme cases, corporate firewalls block access to the DNS ports completely.

To work around this issue, access to a trusted resolver is provided using an SSH tunnel. Here I am assuming that you can use SSH, and you have the IP address of the secure server so that setting up the tunnel is not dependent on the resolver.

Two configurations that support SSH are given. In the first, it is assumed that TLS is not required. In Unbound, if TLS is required for any resolver in a forward-zone, it is required for all resolvers, so this case assumes that TLS is not required for any resolver. SSH only suports TCP connections, and Unbound uses UDP by default for non-TLS connections. So we need to configure Unbound to use TCP for upstream connections. Also, normally Unbound will not connect to localhost for an upstream connection. Both of these issues are resolved in this configuration:

server:
    tcp-upstream: yes
    do-not-query-localhost: no

forward-zone:
    forward-tls-upstream: no
    forward-addr: 127.0.0.1@11053

These settings would be added to those given in the configuration for the secure caching server. In this case I am assuming that an SSH tunnel exists from localhost@11053 to the remote secure server’s port 53. If you are using this configuration, you will likely not need to configure TLS on your secure remote server.

In the second configuration, TLS will be used for all upstream resolvers. In this case, TLS must be properly configured for the secure remote resolver being accessed through the SSH tunnel and your local certificate authority bundle must include the certificate of the authority that issued your TLS certificate. That can either be a public authority, such as Let’s Encrypt, or if you use a self-signed certificate you should add the certificate for your personal authority to your bundle. Once you take care of the certificate, you configure Unbound with the following additional settings:

server:
    do-not-query-localhost: no

forward-zone:
    forward-tls-upstream: yes
    forward-addr: 127.0.0.1@11853#server-name.tld

In this case, replace server-name.tld with a name compatible with your certificate (use openssl to determine CN). This assumes that an SSH tunnel exists from localhost@11853 to the remote secure server’s port 853. This configuration is generally much preferred to the previous configuration if you can properly configure TLS on your remote secure resolver.

Logging into Networks

When connecting to a foreign network for the first time your access to the network is often blocked until you sign in, and the sign-in page is only available through the DHCP provided name server. This is common in coffee shops and hotels, but can happen for any network that controls access. In order to connect to the network, you would need to temporarily stop using your Unbound name server and use the DHCP provided name server instead. Once you have logged into the network, you need to reestablish Unbound as your name server. To do so, you can use the following scripts.

promiscuous_dns:

#!/bin/sh

sudo rm /etc/resolv.conf
sudo sh -c 'cat /var/run/NetworkManager/resolv.conf > /etc/resolv.conf'

secure_dns:

#!/bin/sh

sudo rm /etc/resolv.conf
sudo sh -c 'echo -e "nameserver 127.0.0.1\noptions edns0" > /etc/resolv.conf'
sudo sh -c 'unbound-control flush_zone .'

Use promiscuous_dns when logging into a network, and then secure_dns once you have logged in to switch back to using Unbound.

NetworkManager

If your system uses NetworkManager to manage the network, then it is likely configured to automatically override your /etc/resolv.conf file every time the network changes. Meaning, that when you connect to a wireless network, NetworkManager will rewrite /etc/resolv.conf to use the DHCP supplied name servers rather than your local Unbound server. To stop that from happening, create the file /etc/NetworkManager/conf.d/dns.conf that contains:

[main]
    dns=none

Then restart NetworkManager (this restart only needs to be done once):

systemctl restart NetworkManager