For those who do not know, Domain Name System is the internet component that translate human friendly domain names (like blog.piasecki.it) to IP addresses. The system is hierarchical, and before getting and IP, you need to ask a couple servers first.

  • Recursive DNS resolver – usually handled by your ISP. Alternatively can be provided by third-party services like Google or Cloudflare.
  • Root server lookup – the answer will be authoritative server for top-level domain (TLD)
  • TLD query – this will return authoritative server for specific domain and the subdomains

Unbound is a recursive DNS resolver, so you can use it without relying on 3rd party resolvers.

The main consideration behind using Unbound is privacy – you will not use Google or ISP as a proxy to fetch the IP.

Another Unbound usecase is bypassing content-blocking mechanisms implemented by the ISP. The provider can block the traffic in two ways:

  • DNS blocking – it is very easy to set up and maintain, but not quite effective
  • IP blocking – implementing is challenging. There is a risk of overblocking, for example, when dealing with Content Delivery Networks. It requires constant updates, because sites can change the IP addresses. And it can be bypassed by using the VPN.

Because of that, domain name blocking is most popular content blocking attempt. Unbound can be used to bypass domain blocking by the ISP, by executing those queries directly.

Other unbound features are:

  • Caching – frequently used domains will be fetched faster. It takes about 10-12ms to ping google/cloudflare, but only 3ms for my k3s host
  • Prefetching – when DNS entry is about to expire, Unbound will automatically refresh it, making the subsequent query even faster
  • DNSSEC – guarantee authenticity of DNS record, using public key infrastructure. It also confirms that DNS responses have not modified in transit. I have not configured it yet – I will cover it later.

Without further ado, here is configuration I used:

apiVersion: v1
kind: ConfigMap
metadata:
  name: unbound-main-conf
data:
  unbound.conf: |
    server:
    verbosity: 0
    interface: 0.0.0.0
    port: 53
    do-ip4: yes
    do-udp: yes
    do-tcp: yes

    cache-max-ttl: 86400       # Maximum time (in seconds) to cache a record 
    cache-min-ttl: 0           # Minimum time (in seconds) to cache a record
    msg-cache-size: 50m        # Message cache size
    rrset-cache-size: 100m     # Resource record cache size
    infra-cache-numhosts: 10000  # Number of hosts in the infrastructure cache
    prefetch: yes              # Enable prefetching of popular cache entries
    prefetch-key: yes          # Also prefetch DNSKEY and other key information

    access-control: 0.0.0.0/0 allow   # Allow all IPv4 clients
    access-control: ::0/0 allow       # Allow all IPv6 clients

    do-ip6: no
    prefer-ip6: no
    #root-hints: "/var/lib/unbound/root.hints"
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 1
    so-rcvbuf: 1m

    private-address: 192.168.1.0/24
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: unbound-a-records-conf
data:
  a-records.conf: |
    server:
        # Your internal network name and addresses go here
        private-domain: "your-awesome-domain.local"
        domain-insecure: "your-awesome-domain.local"
        local-zone: "your-awesome-domain.local" transparent

        local-data: "k8s.your-awesome-domain.local IN A 172.30.0.1"
        #local-data-ptr: "172.30.0.1 your-awesome-domain.local"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: unbound-forward-records-conf
data:
  forward-records.conf: |
    forward-zone:
        # Forward all queries (except those in cache and local zone) to
        # upstream recursive servers
        name: "."
        # Queries to this forward zone use TLS
        forward-tls-upstream: yes

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: unbound-srv-records-conf
data:
  srv-records.conf: |
    # SRV records
    # _service._proto.name. | TTL | class | SRV | priority | weight | port | target.
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: unbound-dns
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: unbound-dns
    spec:
      nodeSelector:
        kubernetes.io/arch: amd64
      containers:
        - image: mvance/unbound
          imagePullPolicy: IfNotPresent
          name: dns-unbound
          ports:
            - containerPort: 53
              protocol: UDP
          resources: {}
          volumeMounts:
            - name: unbound-main-conf-volume
              mountPath: /opt/unbound/etc/unbound/unbound.conf
              subPath: unbound.conf
            - name: unbound-a-conf-volume
              mountPath: /opt/unbound/etc/unbound/a-records.conf
              subPath: a-records.conf
            - name: unbound-forward-conf-volume
              mountPath: /opt/unbound/etc/unbound/forward-records.conf
              subPath: forward-records.conf
            - name: unbound-srv-conf-volume
              mountPath: /opt/unbound/etc/unbound/srv-records.conf
              subPath: srv-records.conf
      restartPolicy: Always
      volumes:
        - name: unbound-main-conf-volume
          configMap:
            name: unbound-main-conf
        - name: unbound-a-conf-volume
          configMap:
            name: unbound-a-records-conf
        - name: unbound-forward-conf-volume
          configMap:
            name: unbound-forward-records-conf
        - name: unbound-srv-conf-volume
          configMap:
            name: unbound-srv-records-conf

All that’s left, is to reconfigure Pi-hole: remove external upstream DNS servers, and add Unbound.

And since we are talking about security and privacy in the context of DNS, here are handy filters for Pi-hole:

Leave a Reply

Your email address will not be published. Required fields are marked *

+