([!tag debian DNS]]

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:

  • D'abord parce que ces serveurs DNS sont parfois lents ou ont du mal à répondre. Effectivement, très souvent auparavant, j'avais de vrais de soucis avec les résolveurs DNS de Free. Tout le monde rallait à certain moments de la journée parce qu'il y avait un lag dans les réponses DNS ce qui donnait l'impression qu'Internet était lent !

  • Ensuite, pour des questions de vie privée. Avec toutes les requêtes DNS, mon FAI sait éxactement quels sont les sites que les gens de mon réseau local consultent. Ce n'est pas du tout son affaire de savoir ça. Notamment, ça ne regarde pas mon FAI de savoir que je me connecte à une plate-forme technique hyper-sécurisée du gouvernement. Le travail du FAI que je rémunère est de me fournir un accès à Internet, point. Et ne me dite pas que c'est pour prévenir des problèmes techniques: nous avons tout le loisir de faire remonter les problèmes quand ils surviennent, pas besoin de payer des gens pour faire de la surveillance préventive en scannant et analysant ce qui se passe au niveau DNS (en dehors de la supervision de base du service, bien entendu). Après, il se peut très bien que le FAI ne fasse rien de ces données mais, dans tous les cas, ce n'est pas contractuel, donc le doute est permis.

  • Enfin, c'est surtout parce que ces DNS sont souvent menteurs. Parfois c'est la justice française qui ordonne ça (une forme de censure légale) et dans ce cas, pourquoi pas. Mais c'est surtout que les DNS peuvent conduire à une forme d'abus de pouvoir privé, notamment si le FAI décide de lui même de ne pas répondre à certaines requêtes tout à fait légitimes.

  • Par ailleurs, ce n'est pas comme ça que fonctionne le DNS qui est un système complètement décentralisé: n'importe qui peut monter son propre service qui interroge les serveurs racines et rebondit ensuite sur les différents serveurs qui font autorité sur les domaines.

  • De plus, ça permet de soulager les ressources du FAI. Si tout le monde disposait de son serveur DNS dans sa machin box, il n'y aurait sans doute plus besoin de monter des serveurs monstres pour répondre aux requêtes DNS internes. Bon après, ça chargerait sans doute pas mal les serveurs racines.

  • Et pour terminer: le défi technique ! Se dire qu'on peut monter un service DNS (presque) celui d'un FAI donne l'impression qu'on sait faire des choses avec Internet. Ça peut toujours être un bon point à mettre sur un CV ou lors d'un entretien d'embauche.

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. A 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 recommendent 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]( https://nlnetlabs.nl/projects/unbound/rfc-compliance/).

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 ?

  • Disposer d'un service de résolution DNS pour les clients du réseau local.
  • Le service ne doit pas être public (limité au réseau local).
  • Il doit être disponible en IPv6 et IPv4.
  • Il doit mettre en oeuvre DNS-over-TLS.
  • Il doit mettre en oeuvre le service DNS classique sur UDP et TCP.
  • Il doit faire en sorte de chiffrer au maximum ce qui sort du réseau local.
  • Il doit utiliser de préférence le protocole IPv6 pour les interrogations DNS.
  • Tout ce qui ne sert pas doit être éteint/fermé.

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 de unbound

Sous Debian Buster, unbound est consituté d'au moins deux paquets:

  • unbound qui est le serveur en lui-même.
  • unbound-anchor qui est le programme permettant de récupérer les certificats DNSSEC de la racine DNS.

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 faîtes 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 quon 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 firewall fait que ça ne sert à rien mais j'ai préferé 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 d'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:

  • Par l'API systemd-resolved via une interface D-Bus.
  • Via l'API NSS.
  • Et enfin, via le traditionnel /etc/resolv.conf via un serveur DNS local (le stub), fourni par systemd-resolved sur le port 53 de 127.0.0.53 uniquement (pas en IPv6).

Pour résumer:

  • On déclare les serveurs DNS de base dans /etc/systemd/resolved.conf.
  • Si on a des interfaces réseaux qui utilisent des serveurs DNS spécifiques, on l'indique dans la conf networkd.
  • En utilisant nss-resolve, et un peu de conf, les applis compatibles NSS utiliseront systemd-resolved.
  • Pour tout le reste, on utilisera le stub.

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

A 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. A moins d'être derrière un firewall 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.