DNS-over-TLS à la maison avec Unbound 🔗

Posted by Médéric Ribreux 🗓 In blog/Sysadmin/

Introduction

Depuis quelques années déjà, j'utilise un serveur auto-hébergé qui fait à la fois des choses publiques (genre ce site web) mais également des choses privées, à disposition des machines qui se situent sur mon réseau local.

Parmi ces services privés, j'héberge un service de DNS à la fois local pour le réseau local mais également en tant que résolveur pour les autres machines. Chaque fois qu'une machine a besoin d'un nom public, elle passe par ce service DNS privé qui interroge toute la chaîne DNS avant de renvoyer la réponse aux clients.

Pourquoi faire comme ça me direz-vous ? Pourquoi ne pas utiliser simplement les serveurs DNS du fournisseur d'accès à Internet ? Ma réponse tient en plusieurs points:

Donc, depuis des années, j'utilise un service DNS interne. Pourtant le DNS évolue avec des choses comme DNSSEC, DNS over TLS et, plus récemment, DNS-over-HTTPS.

Je me suis dit qu'il fallait que je commence à m'intéresser à tout ce qui permet de chiffrer les accès clients-serveurs. Je l'ai déjà fait pour la messagerie électronique. À présent, ce sera pour le DNS avec DNS-over-TLS.

Pourquoi DNS-over-TLS et pas DNS-over-HTTPS ? Tout simplement parce qu'en 2019, DNS-over-TLS est la seule solution qui peut s'appliquer à tout un système d'exploitation et pas uniquement au navigateur web.

Cet article a pour objet de présenter l'installation d'un résolveur DNS exploitant DNS-over-TLS.

Quel serveur utiliser en 2019 ?

Traditionnellement, il existe bind dans sa version 9. Je l'ai utilisé pendant de nombreuses années. Il fait le job, reste simple à configurer et est packagé dans Debian. Néanmoins, depuis quelques années, de nombreuses distributions recommandent l'utilisation d'Unbound.

Comme Bind, il est codé en C (ce qui pour un gamin de 12 ans en 1990, signifie le Graal de la puissance brute) ce qui permet de le déployer sur des petites configurations.

Par ailleurs, Unbound gère un grand nombre de RFC sur le DNS.

Et pour terminer, Bind ne gère pas nativement les tunnels TLS. Pour activer DNS-Over-TLS, il faut s'appuyer sur stunnel alors qu'avec Unbound, il n'y a rien à faire.

Qu'est-ce-que nous voulons faire ?

Configuration nftables

Avant de commencer, autant ouvrir les ports DNS (UDP/53 et TCP/53) et DNS-over-TLS (TCP/853) aux clients du réseau local. Mon réseau local est disponible en IPv4 et en IPv6 donc ça donne les éléments suivants:

Pour la chaîne en entrée

#!/usr/sbin/nft -h

# Configuration spécifique d'Unbound
# Ouverture des ports privés en entrée sur le réseau local
add rule inet public_server entree ip saddr 192.168.0.0/24 udp dport { 53 } accept
add rule inet public_server entree ip saddr 192.168.0.0/24 tcp dport { 53, 853 } accept
add rule inet public_server entree ip6 saddr 2a01:e0a:14:9be0::/64 udp dport { 53 } accept
add rule inet public_server entree ip6 saddr 2a01:e0a:14:9be0::/64 tcp dport { 53, 853 } accept

# Ouverture des ports privés en sortie sur le réseau local
add rule inet public_server sortie ip daddr 192.168.0.0/24 udp sport { 53 } accept
add rule inet public_server sortie ip daddr 192.168.0.0/24 tcp sport { 53, 853 } accept
add rule inet public_server sortie ip6 daddr 2a01:e0a:14:9be0::/64 udp sport { 53 } accept
add rule inet public_server sortie ip6 daddr 2a01:e0a:14:9be0::/64 tcp sport { 53, 853 } accept

N'oubliez pas non plus d'ouvrir les ports UDP/TCP/53 et TCP/853 pour que votre serveur puisse faire des requêtes DNS aux serveurs faisant autorité.

Installation d'Unbound

Sous Debian Buster, Unbound est constitué d'au moins deux paquets:

Ici, un simple apt-get install unbound suffit à faire le job.

Gestion des certificats Letsencrypt

Avant de commencer à configurer, n'oubliez pas la gestion des clefs de chiffrement. Si vous faites du DNS-Over-TLS, vous devez disposer d'un certificat en bonne et due forme. Il n'y a pas de miracle. Si vous utilisez Letsencrypt et que vous souhaitez réutiliser le certificat émis par cette AC, il vous faudra ruser un petit peu.

En effet, Unbound est une application "apparmorée": Apparmor gère finement quels fichiers l'application peut accéder et, bien évidemment, Unbound n'a pas accès au répertoire /etc/letsencrypt.

Vous devrez donc amender la configuration apparmor d'unbound en ajoutant le contenu qui suit dans le fichier /etc/apparmor.d/local/usr.sbin.unbound:

# Access to Let's Encrypt keys
/etc/letsencrypt/live/votre_domaine/*.pem r,
/etc/letsencrypt/archive/votre_domaine/*.pem r,

Ensuite, un simple systemctl restart apparmor devrait gérer les problèmes d'accès aux clefs letsencrypt.

Configuration globale d'unbound

Ce qui est pratique avec unbound c'est qu'on peut tout configurer dans un seul fichier ou choisir de séparer la configuration en plusieurs fichiers. C'est d'ailleurs le parti pris de Debian. Donc on va faire avec ce mode de configuration.

La configuration par défaut vous permet de bénéficier d'un résolveur DNS (un truc qui fait les requêtes DNS vers l'extérieur en passant par les serveurs racines) sur localhost.

Mais nous allons modifier cette configuration en ajoutant le fichier /etc/unbound/unbound.conf.d/resolver.conf avec le contenu suivant:

# Configuration du résolveur local server

# Ports et adresses
port: 53
interface: 127.0.0.1
interface: ::1
interface: 192.168.0.4@53
interface: 192.168.0.4@853
interface: 2a01:e0a:14:9be0:bab:babe:bab:baba@53
interface: 2a01:e0a:14:9be0:bab:babe:bab:baba@853
interface-automatic: no
outgoing-interface: 192.168.0.4
outgoing-interface: 2a01:e0a:14:9be0:bab:babe:bab:baba
prefer-ip6: yes
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes

# DNS-over-TLS
tls-service-key: "/etc/letsencrypt/live/votre_domaine/privkey.pem"
tls-service-pem: "/etc/letsencrypt/live/votre_domaine/fullchain.pem"
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
tls-port: 853

# Restriction de service
access-control: 127.0.0.1 allow
access-control: ::1 allow
access-control: 192.168.0.0/24 allow
access-control: 2a01:e0a:14:9be0::/64 allow

# Configuration des logs
verbosity: 1
use-syslog: yes
log-queries: yes
log-servfail: yes

remote-control:
  control-enable: no

Un simple systemctl restart unbound devrait faire le job. Sinon journalctl est votre ami.

Quelques explications toutefois sur cette configuration. D'abord, la partie relative aux interfaces où le service Unbound est disponible. Il y a en effet, de nombreuses déclarations d'interfaces. C'est essentiellement à cause de DNS-Over-TLS car Unbound a besoin qu'on lui indique sur quelles interfaces activer le port 853.

Pour le contrôle d'accès, normalement, la configuration du pare-feu fait que ça ne sert à rien mais j'ai préféré l'ajouter (ceinture et bretelle) au cas où la conf du firewall bouge. Par défaut tout ce qui n'est pas localhost est en mode "refused" (renvoie une réponse DNS REFUSED au client). Mettre en allow les sous-réseaux autorisés suffit à ouvrir le service aux clients des sous-réseaux.

Configuration des clients

Ok, vous êtes moderne et vous avez sytemd installé sur votre système. Vous vous dites donc que tout est correct et configuré correctement out-of-the-box. Mais ce ne sera pas forcément le cas et pour cette hypothèse, mieux vaut comprendre comment faire.

Systemd met à disposition un service de gestion de la résolution de noms DNS qui se nomme systemd-resolved. Ce dernier est configuré à un niveau global dans le fichier /etc/systemd/resolved.conf. Ensuite, si vous utilisez systemd-networkd, chaque carte réseau peut également embarquer sa propre configuration DNS. Néanmoins, tout ce qui sera déclaré dans resolved.conf s'appliquera à tout ce qui n'est pas déjà configuré.

systemd-resolved met à disposition 3 moyens de résolution de noms:

Pour résumer:

Pour l'API par D-Bus, on n'a rien à faire !

Pour NSS, il faut installer le paquet libnss-systemd (mais il devrait déjà être installé de facto). Ensuite, il faut simplement modifier le fichier /etc/nsswitch.conf en ajoutant le module systemd dedans.

Pour info, NSS est une API qui vise à concentrer tout ce qui a trait aux noms qu'on peut trouver sur une machine, que ce soit des noms d'utilisateurs, de groupes, de machines, etc. Pour chaque type (on appelle ça une base de noms), on peut utiliser différents modules qui vont chercher dans des fichiers textes, sur un serveur DNS, sur un serveur LDAP les noms qu'on cherche. L'ordre des modules donne l'ordre de ce qui sera joué en termes de recherche. Par exemple, la ligne:

hosts:  files dns

indique qu'on commence par rechercher des noms de machine dans un fichier (généralement /etc/hosts) puis sur un serveur DNS.

Dans notre cas, il suffit d'ajouter le module systemd à cette ligne pour bénéficier de systemd-resolved:

hosts:  files resolve [!UNAVAIL=return] dns

On place resolve avant dns, car il est possible d'utiliser un fichier resolv.conf différent de celui configuré par la méthode qui suit.

Pour le dernier cas où vos applications n'utilisent ni l'API resolved ni NSS, il vous reste à utiliser le serveur local DNS de systemd-resolved (le stub) en effectuant un lien entre le fichier de configuration dynamique généré par systemd-resolved et le traditionnel /etc/resolv.conf:

# ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

Un truc de base comme la commande host utilisera ce cas de figure.

Bien entendu, voici le contenu de votre fichier /etc/systemd/resolved.conf:

#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See resolved.conf(5) for details

[Resolve]
DNS=ipv6_de_votre_serveur_DNS ipv4_de_votre_serveur_DNS
#FallbackDNS=8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
#Domains=
LLMNR=no
#MulticastDNS=yes
#DNSSEC=allow-downgrade
#Cache=yes
DNSStubListener=yes
DNSOverTLS=opportunistic
ReadEtcHosts=yes

À partir de cet instant, toutes vos requêtes doivent passer par votre serveur DNS via le protocole DNS-over-TLS.

Une tâche pour Ansible

Pour les gens qui utilisent Ansible pour gérer le déploiement de leurs machines, voici un fichier de tâche à insérer où vous voulez dans votre playbook:

---
# Installation et configuration de Unbound en mode résolveur
- name: Unbound installation
  apt:
    name: unbound
    install_recommends: no
    state: latest
    force_apt_get: yes
    autoclean: yes

- name: Unbound nftables configuration
  copy:
    src: nftables/unbound.nft
    dest: /etc/nftables/unbound.nft
    owner: root
    group: root
    mode: 0644

- name: Restart nftables
  systemd:
   name: nftables
   state: restarted

- name: Unbound resolver configuration
  copy:
    src: unbound/resolver.conf
    dest: /etc/unbound/unbound.conf.d/resolver.conf
    owner: root
    group: root
    mode: 0644

- name: Add unbound to ssl-cert group
  user:
    name: unbound
    append: yes
    groups: ssl-cert

- name: Improve apparmor configuration of unbound
  copy:
    src: apparmor.d/usr.sbin.unbound
    dest: /etc/apparmor.d/local/usr.sbin.unbound
    owner: root
    group: root
    mode: 0644

- name: Restart apparmor
  systemd:
    name: apparmor
    state: restarted

- name: Uninstall bind9
  apt:
    name: bind9
    install_recommends: no
    state: absent
    force_apt_get: yes
    autoclean: yes

- name: Restart unbound
  systemd:
    name: unbound
    state: restarted

Les fichiers mentionnés sont ceux présentés dans les paragraphes précédents.

Conclusions

Bon, voilà un sujet bien couvert. Après quelques enquêtes (Apparmor est à prendre en compte très souvent) et un peu de configuration, vous devriez disposer d'un service DNS qui gère à la fois le DNS en clair et DNS-Over-TLS.

Au niveau des limites, vous comprendrez aisément qu'une machine hostile sur votre réseau local pourra quand même étudier les requêtes DNS en clair qui passent car DNS-over-TLS ne s'adresse qu'aux requêtes clients-résolveurs, pas vers des serveurs faisant autorité. Pour ce dernier point, il faudra sans doute que le protocole ADoT (Authoritative DNS over TLS) soit établi dans une RFC définitive.

Il reste d'autres choses à explorer comme DNS-over-HTTPS mais j'ai choisi de ne pas l'implémenter pour l'instant. D'abord parce que les implémentations au niveau des clients se limitent à Firefox dans mon cas, ce qui est bien mais pas suffisant. Ensuite, je pense que DNS-over-HTTPS n'apporte pas grand-chose comparé à DNS-Over-TLS, c'est juste la même chose sauf qu'on tape sur le port 443. À moins d'être derrière un pare-feu nazi, ça ne fait pas plus que DNS-over-TLS. Enfin, les seules implémentations de serveurs DNS-over-HTTPS sont des résolveurs publics, pas les serveurs faisant autorité et ça ne viendra pas plus combler ce manque au niveau du chiffrement du DNS.

En attendant, vous avez quand même de quoi sécuriser plus globalement le flux entre vos clients et votre résolveur interne, ce qui est déjà pas si mal à votre niveau.