Signer sa zone DNS avec OpenDNSSEC

title-image

Publié le 19/05/2020

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 :

dnssec-lithiofr

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, et 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 à 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 à é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 forcement 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.

Publié dans informatique dns dnssec security


captcha