Signer sa zone DNS avec OpenDNSSEC

Publié le 19/05/2020, dans informatique, dns, dnssec, security
Aujourd'hui nous allons voir comment signer notre zone DNS avec DNSSEC, car gérer son DNS faisant autorité c'est rigolo mais y ajouter DNSSEC ça l'est aussi. DNSSEC est le protocole qui permet de sécuriser les données du DNS en signant cryptographiquement les enregistrements de zone DNS.
Je ne vais pas lister ici les bonnes raisons de signer sa zone avec DNSSEC, car il existe de nombreux articles sur le sujet et en 2020 un ou une bon·ne adminsys sait qu'il faut signer ses zones. Il existe également de bons articles techniques (attention toutefois à bien vérifier la version d'OpenDNSSEC utilisée, car les commandes, entre autres, changent entre la version 1 et 2) utiles à lire.
Si vous utilisez FreeBSD, cet article est également disponible sur https://adminblog.foucry.net/posts/info/dnssec/.
Mini-introduction à DNSSEC
Le but de DNSSEC est que l'utilisateur final (ou plutôt son résolveur) puisse établir une chaîne de confiance entre la racine (en qui le résolveur a déjà confiance, car il en connaît les clés au démarrage) et le DNS faisant autorité sur la zone lui donnant la réponse à sa requête.
En théorie le fonctionnement est le suivant :
- Chaque zone signe ses enregistrements DNS avec une clé
- La zone parente possède un enregistrement DNS qui contient le hash de la clé dont la zone enfant se sert pour signer
- Comme on a confiance dans les clés de la racine, on peut vérifier toute la chaîne jusqu'aux enregistrements signés par la clé finale
Par exemple pour lithio.fr :
- On a confiance dans les clés A de la racine et celle-ci signe ses enregistrements avec.
- On interroge la racine qui nous dit d'aller interroger les DNS de fr.
- La racine a aussi un enregistrement qui indique que fr signe avec une clé B
- On interroge fr qui nous dit d'aller interroger les DNS de lithio.fr.
- fr a aussi un enregistrement qui indique que lithio.fr signe avec une clé C.
- On interroge lithio.fr qui nous donne notre réponse en signant avec sa clé C
Au final on a donc la racine qui signe ses réponses avec la clé A, fr qui signe ses réponses avec la clé B et lithio.fr qui signe ses réponses avec la clé C. On a de base confiance en A, A a confiance en B et B a confiance en C : ainsi on fait confiance aux réponses de lithio.fr via toute la chaîne.
Cet exemple sert juste d'explication car dans les faits chaque zone n'a pas une clé mais plusieurs : les KSK (Key Signing Keys) et les ZSK (Zone Signing Key). Les KSK sont comme les clés A, B et C de notre exemple sauf qu'au lieu de signer directement les enregistrements DNS de la zone, elles vont signer des ZSK qui, elles, vont signer les enregistrements de la zone. La chaîne de confiance suit toujours le même principe sauf qu'il y a plus d'élements. À noter que les hash de la clé fille dans la zone parente sont appelés DS (Delegation Signer).
Pour prendre un exemple concret :
Schéma DNSSEC de lithio.fr
Dans ce schéma qui représente la chaîne DNSSEC jusqu'à lithio.fr :
- On a confiance dans la clé qui a l'id 20326 de la racine.
- On interroge la racine qui nous dit d'aller interroger les DNS de fr.
- La racine a bien la clé 20326 qui signe la clé 22545
- La racine a aussi un enregistrement DS signé par 22545 qui indique que fr signe avec la clé 35095
- On interroge fr qui nous dit d'aller interroger les DNS de lithio.fr.
- fr a bien la clé 35095 qui signe la clé 28756
- fr a aussi un enregistrement DS signé par 28756 qui indique que lithio.fr signe avec la clé 65035.
- On interroge lithio.fr
- lithio.fr a bien la clé 65035 qui signe la clé 6378
- lithio.fr nous donne la réponse à notre requête en signant cette réponse avec la clé 6378
On peut également voir cette arborescence en utilisant drill :
$ drill -S lithio.fr SOA
;; Number of trusted keys: 2
;; Chasing: lithio.fr. SOA
DNSSEC Trust tree:
lithio.fr. (SOA)
|---lithio.fr. (DNSKEY keytag: 6378 alg: 8 flags: 256)
|---lithio.fr. (DNSKEY keytag: 65035 alg: 8 flags: 257)
|---lithio.fr. (DS keytag: 65035 digest type: 2)
|---fr. (DNSKEY keytag: 28756 alg: 8 flags: 256)
|---fr. (DNSKEY keytag: 35095 alg: 8 flags: 257)
|---fr. (DS keytag: 35095 digest type: 2)
|---. (DNSKEY keytag: 22545 alg: 8 flags: 256)
|---. (DNSKEY keytag: 20326 alg: 8 flags: 257)
;; Chase successful
Ainsi, en ayant confiance uniquement en une clé de la racine, on peut vérifier les réponses de toute la chaîne.
Pour commencer
Dans cet article nous allons nous intéresser uniquement à la dernière partie (avec lithio.fr). Notre but va être de générer une KSK, de transmettre le DS à la zone parente pour que celle-ci puisse dire que l'on signe avec cette KSK, et d'utiliser la KSK pour signer des ZSK qui vont elles-mêmes signer les enregistrements DNS de notre zone.
Dans cet article je pars du principe que vous gérez déjà votre propre DNS faisant autorité (avec Bind ou NSD par exemple).
Dans le cadre des tests de cet article j'utilise des machines sous Debian 10 et nous allons signer la zone dnssec.lithio.fr (créée et active uniquement pour ces tests), cette zone ainsi que sa zone parente lithio.fr sont gérées par le logiciel NSD.
Nous allons donc devoir :
- Installer les programmes utiles
- Configurer SoftHSM (notre stockage de clés)
- Configurer OpenDNSSEC
- Ajouter notre zone à OpenDNSSEC
- Publier notre zone
- Transmettre le DS à la zone parente
- Signer notre zone
Installation des paquets
On installe OpenDNSSEC et SoftHSM2 :
# apt install opendnssec softhsm2
OpenDNSSEC est le logiciel qui va s'occuper de la signature de la zone, la génération et la rotation des clés en suivant une politique que nous lui indiquerons. SoftHSM2 est un HSM logiciel (car nous n'avons pas de HSM matériel), SoftHSM est utilisé pour stocker des clés cryptographiques.
Configuration de SoftHSM
On crée un slot HSM sur le slot 0 avec l'étiquette (TokenLabel) OpenDNSSEC, il nous demande un code pin administrateur et utilisateur (je mets 1234 pour l'exemple mais bien sûr il faut en mettre des plus sûrs).
# softhsm2-util --init-token --slot 0 --label "OpenDNSSEC"
=== SO PIN (4-255 characters) ===
Please enter SO PIN: ****
Please reenter SO PIN: ****
=== User PIN (4-255 characters) ===
Please enter user PIN: ****
Please reenter user PIN: ****
The token has been initialized and is reassigned to slot 364027858
On peut vérifier les informations avec la commande suivante.
# softhsm2-util --show-slots
Available slots:
Slot 364027858
Slot info:
Description: SoftHSM slot ID 0x15b29fd2
Manufacturer ID: SoftHSM project
Hardware version: 2.4
Firmware version: 2.4
Token present: yes
Token info:
Manufacturer ID: SoftHSM project
Model: SoftHSM v2
Hardware version: 2.4
Firmware version: 2.4
Serial number: 6b9fa0e695b29fd2
Initialized: yes
User PIN init.: yes
Label: OpenDNSSEC
Configuration d'OpenDNSSEC
OpenDNSSEC contient 2 outils principaux qui nous intéressent : « l'enforcer » et « le signer ».
L'enforcer s'occupe de faire appliquer les politiques et le signer, hé bien, signe.
Configuration générale
En éditant /etc/opendnssec/conf.xml.
On modifie le bloc du HSM pour indiquer à OpenDNSSEC où sont stockées nos clés ainsi que le pin :
<Repository name="SoftHSM">
<Module>/usr/lib/softhsm/libsofthsm2.so</Module>
<TokenLabel>OpenDNSSEC</TokenLabel>
<PIN>1234</PIN>
<SkipPublicKey/>
</Repository>
On ne touche pas à la configuration Common qui est bien comme elle est.
Les clés (surtout les ZSK) sont régulièrement changées, le logiciel en génère donc à l'avance pour assurer le remplacement de manière fluide. On va modifier la configuration de l'enforcer, pour générer nos clés 1 jour en avance.
<Enforcer>
<Datastore><SQLite>/var/lib/opendnssec/kasp.db</SQLite></Datastore>
<AutomaticKeyGenerationPeriod>P1D</AutomaticKeyGenerationPeriod>
<WorkingDirectory>/var/lib/opendnssec/enforcer</WorkingDirectory>
<WorkerThreads>2</WorkerThreads>
</Enforcer>
On va modifier la configuration du signer pour signaler – une fois la zone signée – à NSD de recharger la zone (à adapter en fonction du logiciel que vous utilisez).
<Signer>
<WorkingDirectory>/var/lib/opendnssec/signer</WorkingDirectory>
<WorkerThreads>2</WorkerThreads>
<!-- the <NotifyCommmand> will expand the following variables:
%zone the name of the zone that was signed
%zonefile the filename of the signed zone
-->
<NotifyCommand>/usr/sbin/nsd-control reload %zone</NotifyCommand>
</Signer>
On constate également qu'il n'y a pour le moment aucune clé générée :
# ods-hsmutil list
Listing keys in all repositories.
0 keys found.
Repository ID Type
---------- --
Configuration Kasp
KASP pour “Key And Signature Policy” est la configuration qui contient les politiques de génération et renouvellement des clés. Le mieux pour bien en comprendre le fonctionnement est d'en lire la documentation.
On va modifier /etc/opendnssec/kasp.xml.
Nous devons ici éditer la configuration que nous allons utiliser, il y en a de base 2 : default (qui a d'ailleurs comme description A default policy that will amaze you and your friends, comme quoi on rigole bien dans le monde du DNS) et lab. Dans le cadre de ce test je vais utiliser lab, mais en production il est préférable d'en utiliser d'autres, si, par exemple, vous souhaitez signer un domaine dont la zone parente est fr, l'AFNIC a mis à disposition un document sur le sujet.
On voit également qu'on peut indiquer les algorithmes utilisés pour générer les clés, par défaut c'est le 8 (RSA-SHA256) qui est utilisé. Pour savoir quoi mettre la section 3.1 du RFC 8624 est bien utile en nous donnant un tableau qui contient les algorithmes que l'on doit, ne doit pas ou devrait employer. Je vais utiliser ici le 13 (ECDSAP256SHA256) et indique que le renouvellement de la KSK est manuel (car il faudra l'envoyer à la zone parente à la main) :
<KSK>
<Algorithm length="512">13</Algorithm>
<Lifetime>P1Y</Lifetime>
<Repository>SoftHSM</Repository>
<ManualRollover/>
</KSK>
<ZSK>
<Algorithm length="512">13</Algorithm>
<Lifetime>PT4H</Lifetime>
<Repository>SoftHSM</Repository>
</ZSK>
À noter que dans ce fichier les périodes de temps sont indiquées au format ISO 8601 (comme expliqué dans la documentation). À noter également que les valeurs sont fixes : 1Y (un an) vaudra toujours 365 jours, même les années bissextiles; 1M (un mois) vaudra toujours 31 jours, même en février.
On vérifie que notre configuration soit correcte :
# ods-kaspcheck
On initialise la base de données de l'enforcer avec la commande (on ne le fait que la première fois, plus jamais ensuite) :
# ods-enforcer-db-setup
On supprime le fichier /etc/opendnssec/prevent-startup qui empêche le lancement d'OpenDNSSEC tant que celui-ci n'est pas configuré et on lance l'enforcer et le signer DNSSEC :
# systemctl start opendnssec-enforcer
# systemctl start opendnssec-signer
On importe nos politiques :
# ods-enforcer policy import
Normalement tout est prêt pour commencer à gérer nos zones.
Ajout de la zone à signer
OpenDNSSEC prévoit de mettre les fichiers de zones dans /var/lib/opendnssec/unsigned/ et de voir le résultat signé dans /var/lib/opendnssec/signed/, dans notre cas nous avons /var/lib/opendnssec/unsigned/dnssec.lithio.fr.
Avant il fallait renseigner les zones dans le fichier /etc/opendnssec/zonelist.xml mais, comme il nous l'indique, cela a changé depuis la version 2.0 et il faut considérer la base de données de l'enforcer comme référence. Nous allons donc ajouter la zone dans la base puis exporter cette dernière dans le fichier zonelist.xml.
On ajoute notre zone à l'enforcer :
# ods-enforcer zone add --zone dnssec.lithio.fr --policy lab --input /var/lib/opendnssec/unsigned/dnssec.lithio.fr --output /var/lib/opendnssec/signed/dnssec.lithio.fr
Après chaque ajout de zone on joue la commande d'export afin de tenir le fichier zonelist.xml à jour :
# ods-enforcer zonelist export
Notre zone est normalement immédiatement signée et on peut vérifier ça dans les logs et dans le fichier de sortie.
On peut voir les clés dans notre HSM :
# ods-hsmutil list
Listing keys in all repositories.
9 keys found.
Repository ID Type
---------- -- ----
SoftHSM 1aa9234b8deeb67654e0a54073f1810f ECDSA/256
SoftHSM 4fc296406405101f2ff07d5b57dbc3f9 ECDSA/256
SoftHSM 15be78d2934f0fffdbb56cf7be69e50a ECDSA/256
SoftHSM 8b03481c104686d88c7e59d09043e776 ECDSA/256
SoftHSM b53d288301fcaf678bffb152cf13ff80 ECDSA/256
SoftHSM a755e62000e11e194c7d93a3b2c09395 ECDSA/256
SoftHSM 3f81735b5e1913ccfd0c15adafae47d9 ECDSA/256
SoftHSM 45a1dddc8e01d69c62de4a956efbd419 ECDSA/256
SoftHSM 2747f82955fea88a4495b94fc4f001be ECDSA/256
À présent il faut indiquer à notre logiciel de DNS faisant autorité sur la zone d'utiliser ce nouveau fichier contenant les signatures DNSSEC.
On vérifie ensuite que les enregistrements RRSIG soient bien présents avec dig (en demandant directement à un serveur faisant autorité sur la zone pour éviter une réponse provenant du cache du résolveur) :
$ dig @dnssec.lithio.fr +dnssec dnssec.lithio.fr
; <<>> DiG 9.11.5-P4-5.1-Debian <<>> +dnssec dnssec.lithio.fr
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15101
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;dnssec.lithio.fr. IN A
;; ANSWER SECTION:
dnssec.lithio.fr. 56 IN A 54.37.69.174
dnssec.lithio.fr. 56 IN RRSIG A 13 3 60 20200102143140 20200102123044 17006 dnssec.lithio.fr. CNz7ipxye839NwTm2C3xXpCisJaSWBpfgCe1ZMCakmZc0NZ+rODXPWi/ Z1E25CkuKEa86Rg/NGl3tcU56K2o0A==
On note qu'il n'y a pas le flag ad, indiquant que le résolveur n'a pas validé avec DNSSEC. C'est normal, car nous demandons directement à un serveur faisant autorité, si nous demandions à un résolveur validant DNSSEC nous ne l'aurions pas non plus, car la zone parente ne possède pas encore l'enregistrement DS prouvant que notre zone doit être signée.
Publier l'enregistrement DS
On vérifie l'état de notre zone :
# ods-enforcer key list --verbose --all --zone dnssec.lithio.fr
Keys:
Zone: Keytype: State: Date of next transition: Size: Algorithm: CKA_ID: Repository: KeyTag:
dnssec.lithio.fr KSK publish 2020-01-02 14:40:44 512 13 2747f82955fea88a4495b94fc4f001be SoftHSM 54859
dnssec.lithio.fr ZSK ready 2020-01-02 14:40:44 512 13 1aa9234b8deeb67654e0a54073f1810f SoftHSM 17006
La KSK est en état publish, on attend qu'elle passe en état ready (on voit qu'elle attend qu'on lui indique que le DS est bien visible) :
# ods-enforcer key list --verbose --all --zone dnssec.lithio.fr
Keys:
Zone: Keytype: State: Date of next transition: Size: Algorithm: CKA_ID: Repository: KeyTag:
dnssec.lithio.fr KSK ready waiting for ds-seen 512 13 2747f82955fea88a4495b94fc4f001be SoftHSM 54859
dnssec.lithio.fr ZSK active 2020-01-02 18:30:44 512 13 1aa9234b8deeb67654e0a54073f1810f SoftHSM 17006
On exporte alors son DS :
# ods-enforcer key export --zone dnssec.lithio.fr --ds
;ready KSK DS record (SHA256):
dnssec.lithio.fr. 3600 IN DS 54859 13 2 b0f3c176f7f8d5a8712f87582b462d4e7f7450da7a8f24e2f2bb71c0fc212356
On le transmet à la zone parente. Dans mon cas je l'ajoute à la zone lithio.fr mais sinon, il faut en général le transmettre via votre bureau d'enregistrement, qui doit avoir un formulaire pour cela.
Une fois le temps de juvénisation passé, on indique à l'enforcer que l'enregistrement DS a été « vu » et que l'on peut signer avec en précisant la KSK en question (son keytag) :
# ods-enforcer key ds-seen --zone dnssec.lithio.fr --keytag 54859
1 KSK matches found.
1 KSKs changed.
On constate que notre KSK est à présent active :
# ods-enforcer key list -v --all --zone dnssec.lithio.fr
Keys:
Zone: Keytype: State: Date of next transition: Size: Algorithm: CKA_ID: Repository: KeyTag:
dnssec.lithio.fr KSK active 2020-01-02 18:30:44 512 13 2747f82955fea88a4495b94fc4f001be SoftHSM 54859
dnssec.lithio.fr ZSK active 2020-01-02 18:30:44 512 13 1aa9234b8deeb67654e0a54073f1810f SoftHSM 17006
On vérifie avec dig, normalement notre zone est à présent signée et le flag ad présent (attention à ce que le résolveur utilisé ne transmette pas une réponse du cache) :
$ dig +dnssec dnssec.lithio.fr
; <<>> DiG 9.11.5-P4-5.1-Debian <<>> +dnssec dnssec.lithio.fr
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3212
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;dnssec.lithio.fr. IN A
;; ANSWER SECTION:
dnssec.lithio.fr. 60 IN A 54.37.69.174
dnssec.lithio.fr. 60 IN RRSIG A 13 3 60 20200102143140 20200102123044 17006 dnssec.lithio.fr. CNz7ipxye839NwTm2C3xXpCisJaSWBpfgCe1ZMCakmZc0NZ+rODXPWi/ Z1E25CkuKEa86Rg/NGl3tcU56K2o0A==
On peut utiliser des outils comme dnsviz ou drill pour vérifier que tout marche bien.
Monitoring
Il convient bien sûr de surveiller nos signatures DNSSEC afin de voir si un souci arrive, pour cela on peut consulter la section 3.5 du document de l'AFNIC.
Stéphane Bortzmeyer a également fait un article sur le sujet avec Icinga.
Il existe d'autres solutions mais ce n'est pas l'objet de cet article.
Conclusion
Maintenant que notre zone DNS est signée, on peut avoir une confiance raisonnable dans les informations qui s'y trouvent (si on utilise un résolveur validant bien sûr), ce qui n'était pas forcément le cas avant.
Les KSK rollover et changements d'algorithmes feront peut-être l'objet d'un futur article (oui c'est risqué de dire ça je sais).
Merci à Jacques Foucry, Imriel, Stéphane Bortzmeyer et Baptiste Dauphin pour leur relecture et avis.
Vous pouvez commenter en envoyant un mail via ce bouton (votre adresse ne sera pas publié).
Commenter par mail