Première rédaction de cet article le 7 avril 2014
Dernière mise à jour le 15 mars 2019
Le système DNSSEC permet d'authentifier les données distribuées via le DNS et protège donc ainsi des attaques par empoisonnement. C'est un outil nécessaire dans la boîte à outils de sécurisation du DNS. Mais rien n'est gratuit en ce bas monde : la cryptographie protège mais elle complique les choses et crée des risques. Parmi ceux-ci, le risque d'expiration des signatures. Il est donc nécessaire de superviser ses signatures DNSSEC. Comment ? Et avec quoi ?
Si vous voulez une introduction à DNSSEC en français, vous pouvez lire mon exposé à JRES. Notez-y un point important : les signatures des enregistrements DNSSEC expirent au bout d'un moment et il est donc nécessaire de re-signer de temps en temps. Si ce processus de re-signature ne marche pas, les signatures vont finir par expirer, rendant le domaine inutilisable. Voici un exemple de signature DNSSEC :
% dig +dnssec A www.bortzmeyer.org ... ;; ANSWER SECTION: www.bortzmeyer.org. 68585 IN A 204.62.14.153 www.bortzmeyer.org. 68585 IN RRSIG A 8 3 86400 20140414143956 ( 20140325120748 15774 bortzmeyer.org. dgJ3BOjUz3hdlRWEbcFK14Jqyl+az/tas/dKEcBs/2QK 4vUd2VihXtEmpLQ6D+FVtMh6n7OubrEpLezEGkHtXKOe 3FO6l+EhrjH82BwjGGnd50RMNDHGk8IR0TOsOj/cNGZM V4Gj24mOV5ANbYWxlqWXYPl9BVi81MVhluw9sas= ) ...
On voit dans la réponse l'adresse IPv4 du
serveur et la signature (enregistrement
RRSIG
). dig formate la
signature de manière lisible par un humain, avec une date de mise en
service (inception) au 25 mars et une date
d'expiration au 14 avril. Ce jour-là, à 14h39
UTC, la signature expirera (le but est d'éviter
les attaques par rejeu). Il faudra donc,
avant, signer à nouveau. Dans le cas de
bortzmeyer.org
, c'est fait avec
OpenDNSSEC. On pourrait aussi lancer un
dnssec-signzone
ou bien un
ldns-signzone
depuis
cron. Ou laisser BIND
prendre cela en charge avec son mécanisme de signature automatique. Mais toutes ces solutions ont un point
commun, elles sont fragiles. Pendant des mois, elles fonctionnent
puis, un jour, un léger changement fait qu'elles ne marchent plus et
qu'on risque de ne pas s'en apercevoir. Un exemple qui m'était arrivé en
changeant de version de Debian : la
bibliothèque de SoftHSM avait changé
d'emplacement, le fichier de configuration d'OpenDNSSEC pointait donc
au mauvais endroit et le signeur d'OpenDNSSEC ne tournait donc
plus. Quelques temps après, bortzmeyer.fr
expirait...
Le problème n'est pas spécifique à DNSSEC. Dans toutes les solutions de sécurité, il y a des dates limites, conçues pour éviter qu'un méchant qui aurait mis la main sur des informations secrètes puisse les utiliser éternellement. C'est par exemple pour cela que les certificats X.509 ont une date d'expiration (attention, avec DNSSEC, les clés n'expirent pas, seules les signatures le font). Comme le savent les utilisateurs de HTTPS, il est très fréquent que le webmestre oublie de renouveler les certificats et paf. À part des bonnes procédures (rappel mis dans l'agenda...), la solution est de superviser. Tout responsable sérieux d'un site Web HTTPS supervise l'expiration. Il faut faire la même chose pour DNSSEC.
La solution que je vais montrer ici fonctionne avec ma configuration Icinga mais elle ne repose que sur des outils compatibles avec l'API Nagios donc elle devrait marcher avec beaucoup d'outils de supervision.
Après plusieurs essais (voir les notes de ces essais plus loin), j'ai choisi comme outil de base le script de test de Duane Wessels. Ses spécifications collent parfaitement à ce que je veux : il se connecte à tous les serveurs DNS d'une zone, demande les signatures, regarde les dates d'expiration et peut signaler un avertissement ou une erreur selon des seuils choisis par l'utilisateur. Un exemple à la main :
% perl check_zone_rrsig_expiration -Z nic.fr ZONE OK: No RRSIGs expiring in the next 3 days; (1.04s) |time=1.042905s;;;0.000000
On peut choisir les seuils, donc mettons qu'on veut un avertissement s'il reste moins d'une semaine :
% perl check_zone_rrsig_expiration -Z nic.fr -W 7 ZONE WARNING: MX RRSIG expires in 3.7 days at ns6.ext.nic.fr; (0.28s) |time=0.281515s;;;0.000000
Pour installer et exécuter ce script, il faut
Perl et certains modules indiqués dans la
documentation. Sur ma machine Arch Linux, ils
n'étaient pas en paquetage standard, il faut donc utiliser AUR, un
dépôt non officiel, accessible avec les commandes
pacaur
ou yaourt
:
% yaourt -S perl-net-dns perl-net-dns-sec
Attention, si vous n'installez pas tous les paquetages indiqués dans la documentation, vous aurez un message pas clair du tout :
*** WARNING!!! The program has attempted to call the method *** "sigexpiration" for the following RR object:
Une fois le programme correctement installé, je vous recommande
l'option -d
si vous voulez déboguer en détail ce
qu'il fait.
On configure ensuite Icinga, par exemple :
object Host NodeName { import "generic-host" address = "127.0.0.1" address6 = "::1" vars.role = "AuthDNS" [...] } object CheckCommand "CheckDNSSEC" { import "plugin-check-command" command = [ PluginDir + "/check_zone_rrsig_expiration" ] arguments = { "-Z" = "$zone$" "-W" = "$warn$" "-C" = "$crit$" } vars.warn = "14" vars.crit = "7" } apply Service "DNSSEC" { check_command = "CheckDNSSEC" check_interval = 86400 vars.zone = "example.org" assign where host.vars.role == "AuthDNS" }
(Merci à Xavier Humbert à ce sujet.) Cette configuration était pour Icinga 2. Pour Icinga 1 :
define command { command_name check-zone-rrsig command_line /usr/local/sbin/check_zone_rrsig_expiration -Z $HOSTADDRESS$ -W $ARG1$ -C $ARG2$ } ... define service { use dns-rrsig-service hostgroup_name My-zones service_description SIGEXPIRATION # Five days left: warning. Two days left: panic. check_command check-zone-rrsig!5!2 } define host{ name my-zone use generic-host check_command check-always-up check_period 24x7 check_interval 5 retry_interval 1 max_check_attempts 3 contact_groups admins notification_period 24x7 notification_options u,d,r register 0 } define hostgroup{ hostgroup_name My-zones members bortzmeyer.org,bortzmeyer.fr,etc-etc } define host{ use moi-zone host_name bortzmeyer.fr }
Une fois que c'est fait, on redémarre Icinga. Ici, voici un test avec
une zone délibérement cassée (elle a été signée manuellement en
indiquant la date d'expiration ldns-signzone -e
20140322100000 -o broken.rd.nic.fr. broken.rd.nic.fr
Kbroken.rd.nic.fr.+008+15802
et sans re-signer
ensuite). Icinga enverra ce genre d'avertissement :
Notification Type: PROBLEM Service: DNSRRSIG Host: broken.rd.nic.fr Address: broken.rd.nic.fr State: WARNING Date/Time: Fri Mar 28 06:50:38 CET 2014 Additional Info: ZONE WARNING: DNSKEY RRSIG expires in 1.2 days at ns2.bortzmeyer.org: (1.12s)
Puis un CRITICAL
puis, lorsque la zone aura
vraiment expiré :
Notification Type: PROBLEM Service: DNSRRSIG Host: broken.rd.nic.fr Address: broken.rd.nic.fr State: CRITICAL Date/Time: Mon Mar 31 09:10:38 CEST 2014 Additional Info: ZONE CRITICAL: ns2.bortzmeyer.org has expired RRSIGs: (1.10s)
Si on re-signe à ce moment, le problème disparait :
Notification Type: RECOVERY Service: DNSRRSIG Host: broken.rd.nic.fr Address: broken.rd.nic.fr State: OK Date/Time: Mon Mar 31 09:40:38 CEST 2014 Additional Info: ZONE OK: No RRSIGs expiring in the next 3 days: (1.04s)
Et, dans le journal d'Icinga, cela apparaitra :
[Fri Mar 21 21:40:32 2014] SERVICE ALERT: broken.rd.nic.fr;DNSRRSIG;CRITICAL;SOFT;2;ZONE CRITICAL: DNSKEY RRSIG expires in 0.6 days at ns2.bortzmeyer.org: (1.09s) ... [Fri Mar 21 21:42:32 2014] SERVICE ALERT: broken.rd.nic.fr;DNSRRSIG;OK;SOFT;3;ZONE OK: No RRSIGs expiring in the next 7 days: (0.68s)
Pour les utilisateurs d'OpenDNSSEC, le
paramètre important à régler en même temps que les seuils de la
supervision est le paramètre
<Refresh>
. Comme le dit la
documentation :
« The signature will be refreshed when the time until the signature
expiration is closer than the refresh interval. »
Donc, en pratique, c'est la durée qu'il faut indiquer avec l'option
-C
(seuil critique). Attention, OpenDNSSEC
ajoute de légères variations (jitter).
J'ai indiqué plus haut qu'il y avait des alternatives à la solution finalement choisie. Il en existe même une liste sur le site d'Icinga. Voici quelques pistes avec mes commentaires.
J'aurais pu développer une solution complète avec Python et dnspython qui a une bonne gestion de DNSSEC. J'ai réalisé un prototype mais, dans la vie, il faut savoir reconnaître un logiciel meilleur que le sien.
Il y a un outil développé par les gens de
.se
, dnssec_monitor. Écrit
en Perl, il a les mêmes dépendances que le script choisi. Mais je
n'arrive pas réellement à comprendre comment il s'intègre avec Nagios.
Il existe un outil en Ruby dans OpenDNSSEC (il est même décrit en détail dans la documentation d'Icinga). Il a pas mal de dépendences Ruby donc j'ai renoncé.
L'outil nagval est très bien mais il n'a pas le même cahier des charges et, notamment, il ne permet pas de tester l'expiration qui va survenir.
Il existe un énorme
ensemble de programmes de tests Nagios qui semble intéressant,
et qui compte un check_dnssec_expiration
. Il se
compile bien :
% ./configure --without-man % make
mais l'exécution est incohérente, une fois sur deux :
% ./dns/check_dnssec_expiration -v -D nic.fr -w 20 -c 3 CRITICAL - SOA is not signed.
La bogue a été signalée à l'auteur mais pas encore résolue. Il semble
que l'outil soit très sensible au résolveur utilisé et qu'il faille
forcer (via resolv.conf
ou via l'option
-H
) un résolveur rapide et fiable.
Mat a également un
script à lui en awk (avec une version en shell). Mais il est à mon avis très dépendant d'un environnement
local, pas utilisable tel quel, sans sérieuses modifications, à mon
avis. Je cite l'auteur : « c'est tout à
fait adaptable, il suffit de remplacer /usr/bin/make -VSIGNED
par la
liste des fichiers de zones signés et /usr/bin/make -VSIGNED:R:T
par
l'ensemble des noms des zones. SIGNED
étant défini dans le Makefile
comme
SIGNED!= find -s * -name '*.signed'
. ».
Du même auteur, un outil permet de tester des fichiers, pas facilement des zones vivantes :
% cat *.signed | awk -f check-expire.awk
Pour tester une zone vivante, il faut que le transfert de zone soit autorisé :
% dig +noall +answer axfr @$SERVERNAME $ZONE | awk -f check-expire.awk
Je n'ai pas encore testé l'outil check_dnssec_expiry.
Enfin, SURFnet a développé un outil (qui dépend de ldns) mais que je n'ai pas réussi à compiler :
... cc -lcrypto `ldns-config --libs` -o sigvalcheck sigvalcheck.o rrsig_valcheck.o query.o rrsig_valcheck.o: In function `ldns_rrsig_time_until_expire': /home/stephane/tmp/sigvalcheck-0.1/rrsig_valcheck.c:42: undefined reference to `ldns_rr_rrsig_expiration' /home/stephane/tmp/sigvalcheck-0.1/rrsig_valcheck.c:41: undefined reference to `ldns_rdf2native_time_t'
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)