Aujourd'hui 1er novembre 2017, mon domaine internet 22decembre.eu a une nouvelle clé DNSSEC (keytag 20243) et il commence à être signé par une autre (keytag 45395). C'est en fait le résultat de mon projet Ldnscript que je vais vous décrire ci-dessous.

stephane@blackblock:/var/ldnscripts ldnscript status 22decembre.eu
### zsk
## generated
creation:       Nov  1 05:30:01 2017 K22decembre.eu.+010+20243.generated
## private
creation:       Oct  1 16:05:19 2017 K22decembre.eu.+010+45395.private
## retire
creation:       Oct  1 16:01:56 2017 K22decembre.eu.+010+50516.retire

### ksk
## generated
stat: *.generated: No such file or directory
## private
creation:       Oct  1 16:01:56 2017 K22decembre.eu.+010+55059.private
## retire
creation:       Oct  1 16:01:56 2017 K22decembre.eu.+010+49366.retire

Last signature:         Nov  1 05:30:01 2017 /var/nsd/signed/22decembre.eu
Zone is verified and complete

Divers outils

Il existe divers outils pour signer sa zone DNS (donc obtenir une zone DNSSEC en quelque sorte) sous OpenBSD:

OpenDNSSEC

... qui est une usine à gaz, absolument pas KISS, mais qui fait le job. Je l'ai utilisé durant quelques temps, et je ne l'aime pas du tout. J'ai beaucoup de mal à le prendre en main, d'où l'intérêt de cet article. Il est présent dans les ports et s'installe donc correctement si le système OpenBSD est propre. Il a dans ses dépendances LDNS.

Knot

C’est en fait un serveur DNS faisant autorité. Il renouvelle automatiquement les clés et fait les signatures, puis sert la zone générée. Il est présent dans les ports. Je ne l’ai jamais testé.

Bind

Bind a un outil dnssec-signzone. Il fait la même chose que Knot, les outils DNSSEC (signeur, générateur de clés) sont juste des binaires externes.

ZKT

Je l'utilisais jusqu’à présent et le recommande dans une certaine mesure. C’est KISS: c’est simple, ça utilise la hiérarchie du système de fichiers pour les domaines récursifs. Il utilise dnssec-signzone et le reste des outils de Bind.

Le problème avec ce dernier logiciel est qu’il évolue peu. Je n’ai pas l'impression qu’on verra apparaître des courbes Edward prochainement. Ce serait pourtant positif. D'autre part, le port n'est pas maintenu (Thuban a demandé une mise à jour, qui consisterait juste au passage à la dernière version du logiciel, pas de réponse pour l'instant).

Je restais donc toujours plus ou moins à l'écoute d'autres systèmes pour maintenir ma zone DNSSEC, et j’ai (re)découvert récemment Ldns, qui de plus est maintenant disponible dans les ports.

Je me suis donc décidé à écrire un script (au départ un ensemble de scripts, d'où le s à la fin, c'est devenu un meta-script) pour gérer et signer mes zones. J'ai eu l'aide de Thuban et PengouinBSD sur quelques aspects non-négligeables.

Objectifs

L'outil doit être simple, donc facile à comprendre, administrer, debugguer. Mieux vaut avoir une légère sur-utilisation de ressources (signer un peu trop souvent, avoir trop de clés en zone...) plutôt que construire un truc sophistiqué truffé d'options avec X bugs potentiels. DNSSEC en lui-même donne déjà bien assez d’occasions de se gourer.

Le minimum de dépendances possible. On doit utiliser au mieux les outils natifs et la structure du système (repertoires, cron, scripts monthly, weekly, daily, NSD).

Ce script est conçu pour des cas simples, soit des petites zones à un seul niveau. J'imagine, peut-être faussement, que ça correspond à la majorité des détenteurs de domaines, qui n'ont donc pas besoin de logiciels complexes pour gérer leur bazar. J'ai donc pris également des décisions de conception en conséquence, qui je l'espère, seront suffisantes pour cette même majorité d'utilisations.

Ce que cet outil ne fait pas.

Ce script ne sait pas gérer (spontanément s'entend) de zones à profondeurs multiples. Néanmoins elles restent possibles en théorie. Il suffit juste de copier le contenu du fichier DS de la clé KSK dans le fichier source de la zone parente.

Je n'ai pas encore non plus de date de validité pour les clés (pour les signatures en revanche, oui).

Ldnscript

J'appelle ce script ldnscript. Il est disponible dans le depot git de framagit et je vous encourage à y contribuer, que ce soit en proposant des patches ou rapportant des bugs.

Il est rédigé de base avec le Korn Shell d'OpenBSD.

Je place ce script sous licence ISC, soit la licence d'OpenBSD à la date de parution.

Installation

Il vous faut le paquetage ldns-utils (présents dans les dépôts OpenBSD) :

$ doas pkg_add ldns-utils

L'installation peut se faire n’importe où (/var, /usr, /etc...). Il doit juste disposer d’un dossier dédié qui contiendra le script, le fichier de configuration général ainsi qu’un sous-dossier où seront gérées les différentes zones (différent de celui où se trouvent les sources des zones). Pour les besoins de la description, ce dossier sera /var/ldnscripts. Mais il devrait à priori fonctionner partout. Si ce n’est pas le cas, merci de remonter le bug.

$ cd /var
$ doas mkdir ldnscripts
$ doas chown $USER ldnscripts
$ git clone https://framagit.org/22decembre/ldnscripts.git

Conf

Warning : ce script est fourni sans garantie aucune. J'attends de vous que vous ayez une certaine intelligence critique pour comprendre ce qu'il se passe, comment ça marche, ce que ça fait. Il faut que vous connaissiez un minimum DNSSEC (et donc DNS), sinon vous vous planterez.

La configuration de l’outil se fait via un fichier conf dans son dossier:

/var/ldnscripts/conf

# repository where to find unsigned zone file and specific conf
NS_REP=/etc/ns

# serial : unixtime
# Cannot make it other currently. I could make it YYYYMMDDnn, but how ?
SERIAL=unixtime

# algorithm to use. They are listed : ldns-keygen -a list
ALG=RSASHA512

# length of the zsk
ZSK_BITS=1024

KSK_BITS=2048

# validity of signatures in days
VALIDITY=7

#NSEC3
NSEC3_ALG=SHA-1
RUN=24

# Verbose - set to 1 if needed
VERBOSE=0

Le fichier indique notamment (paramètre NS_REP) où doivent être récupérés les fichiers sources des zones DNS. Ces fichiers seront au format classique bind et le nom du fichier sera celui de la zone.

Vous pouvez également changer l'algorithme, les longueurs de clés, la durée de validité des signatures (durée en jours uniquement pour l'instant).

Le paramètre SERIAL indique la forme que prendra le numéro de série de la zone. Les possibilités sont unixtime, soit le timestamp unix du moment où vous (re)signerez votre zone ; date, soit la date au format AAAAMMJJxx où xx est le nombre de changement dans la journée ou incremental, soit un bête compteur augmentant à chaque itération.

Le paramètre RUN indique combien de fois doit tourner l'algorithme de NSEC3 pour générer des signatures. J'ai assumé que vous voudriez utiliser NSEC3 car c'est quand même un peu le mieux.

/etc/ns/example.com

$TTL    1D
$ORIGIN example.com.
@       IN      SOA     ns.example.com. root.example.com. (
                        1405492580      ; Serial
                                1D     ; Refresh
                                2H      ; Retry
                                2W     ; Expire
                                2D )       ; Negative Cache TTL

@               IN      NS      ns.example.com.
@               IN      NS      ns6.gandi.net.
@               IN      NS      ns1.gratisdns.dk.


@               IN      MX 10   mail.example.com.

@               IN      TXT     "v=spf1 a:mail.example.com a:mail.example.com -all"
_dmarc          IN      TXT     "v=DMARC1;p=reject;rua=mailto:root@example.com;ruf=mailto:root@example.com;adkim=s;aspf=s;pct=20;rf=afrf;sp=reject"

mail            IN      A       192.0.2.4

host            IN      A       192.0.2.2
                IN      AAAA    2001:db8::45:b

router          IN      A       192.0.2.1
                IN      AAAA    2001:db8::45:dec

photos          IN      CNAME host
www             IN      CNAME host

Si vous utilisez un $ORIGIN, vous devrez alors le mettre en tête de fichier. Sinon ça buggue. Si vous voulez obstinément mettre le $ORIGIN plus loin, pauvre humain, prière de m'envoyer le patch.

Chaque zone peut avoir sa configuration particulière via un fichier de conf (mêmes options possibles que le fichier général). Le nom du fichier sera celui de la zone.conf.

/etc/ns/example.com.conf

# algorithm to use. They are listed : ldns-keygen -a list
ALG=RSASHA512

# length of the zsk
KSK_BITS=4096

J'ai mis ici une longueur de clé de 4096. C'est un truc de barbare incivilisé. C'était pour l'exemple.

Vous avez ici un élément très important du système : la durée de validité des signatures (paramètre VALIDITY), qui doit être au moins aussi long que l'intervalle de ces mêmes signatures (cf la commande signing plus bas).

NSD

Vous devez configurer vous-même NSD pour lui indiquer l'emplacement des fichiers de zone signés, qui sont, par défaut /var/nsd/signed/$ZONE.

/var/nsd/etc/nsd.conf :

## master zones
zone:
    name: "zone.tld"
    zonefile: "signed/zone.tld"

Description des commandes

Ldnscript prend deux arguments. Le premier indique l'action à réaliser, le second le domaine en question. Par exemple:

# ldnscript ksk example.com

Si vous donnez all comme nom de domaine, la commande sera lancée pour tous les domaines gérés par le script. Ici, par exemple, on resigne l'ensemble des domaines :

# ldnscript signing all

rollover

La commande rollover va gérer l'évolution des clés au cours du temps. Elle est destinée à être invoqué chaque mois par le script monthly.

# ldnscript rollover example.com

Ou, pour faire plus mieux (dans /etc/monthly.local)…

for dir in `ls /var/ldnscripts/zones/`; do
    /var/ldnscripts/ldnscript rollover $dir
done

Ce script repose sur la philosophie du Pre-Publish : on publie les clés et on attend patiemment qu'elles soient connues et répandues dans les résolveurs pour les mettre en activité. De même on attend que leurs signatures aient expiré pour les effacer.

Le script va ainsi successivement décaler chaque clé sur sa ligne de vie (les clés inactives seront effacées, les clés actives seront déclarées inactives, les clés générées précédemment seront déclarées actives, et une nouvelle clé sera crée – avec la commande zsk décrite en dessous).

Pour conserver l'état des clés, leur extension est modifiée en generated si elles viennent d'être créées, ou retire si elles sont en fin de vie. De cette facon, seule la clé private peut être utilisée lors des signatures.

Au passage, les clés surnuméraires sont effacées s'il y en a. La dernière clé générée sera mise en activité pour signature (sur le principe que si l'administrateur l'a créée, c'est bien pour qu'elle soit utilisée à la place de la clé prévue auparavant).

Si vous calculez correctement, une zone qu’on laisse tranquille aura, en « vitesse de croisière » trois clés ZSK:

  • Une, générée lors du 1er du mois courant et qui sera utilisée pour signer au cours du prochain mois, generated.
  • Une en cours d’utilisation, private.
  • Une, mise en retraite lors du 1er du mois courant, retire et qui sera effacée lors du prochain rollover.

Ceci assure que les clés seront toujours valides et normalement répandues dans les résolveurs qui connaissent la zone quand on commence à signer avec (pour peu que la durée de validité des signatures ne soit pas absurdement longue).

De même les signatures seront obligatoirement expirées quand on effacera les clés considérées.

Si l'administrateur a générée une nouvelle clé KSK (avec la commande ksk ci après), alors la clé KSK actuelle sera mise à la retraite, et la nouvelle clé sera mise en service. Les éventuelles clés KSK dans un état retire (suite logique de ce qui s'est passé à la ligne immédiatement supérieure, mais bien un mois plus tard), seront elles aussi effacées.

Finalement, le rollover se concluera par une signature (cf plus bas).

NB : il m'a semblé qu'un renouvellement mensuel des clés ZSK était raisonnable et pouvait correspondre à la majorité des domaines où ce script serait utilisé, et de plus la procédure monthly d'OpenBSD rend ceci d'autant plus facile. Vous pouvez également déclencher ça moins fréquemment en plaçant une tâche cron tous les deux mois, tous les trois mois… Mais elle doit toujours se passer au début du mois. Si ça ne vous plait pas, prière d'en débattre poliment dans les commentaires ou dans les pages du dépôt git.

NB : si vous voulez que ce soit plus fréquent, il faudrait modifier le code à cause de la fonction de sécurité (qui bloque la génération de clés après le 15 du mois).

ksk|zsk

L'utilisation d'une de ces deux commande déclenche la génération d'une clé suivant la configuration écrite.

# ldnscript ksk example.com


# ldnscript zsk example.com

Pour parer à toute éventualité, le script refusera de créer une nouvelle clé au delà du 15 du mois. En effet, si le script est invoqué trop tard dans le mois, on prend le risque de mettre en activité une clé avant qu’elle ne soit connue des résolveurs.

On compte également sur l’administrateur pour s’assurer qu’une nouvelle clé KSK soit bien enregistrée dans le registre au moment de sa mise en activité. Il a minimum 15 jours pour ça, ça devrait le faire.

Lors de la création d’une clé, le script va nommer sa clé privée avec son état (generated) en extension dans /var/ldnscripts/zones/$ZONE/$TYPE/. Par exemple :

/var/ldnscripts/zones/test.tld/zsk/Ktest.tld+63642.generated

signing

Signing s’occupe des signatures de la zone. Il doit être invoqué après une génération de clé ou un changement dans la zone, ainsi que de manière régulière suivant le paramètre VALIDITY.

# ldnscript signing example.com

Il va tout d’abord créer un nouveau fichier de zone depuis le source avec un numéro de série adéquat (ldns-signzone ne génère pas encore le fichier final avec numéro de série mis à jour), y ajouter toutes les clés publiques présentes dans la zone, quelque soit leur état et créer un fichier signé dans /var/ldnscripts/zones/$ZONE/signed. Ce fichier sera vérifié (via la même fonction que dans status, voir plus bas) et s'il est valide, il sera alors copié dans /var/nsd/signed/$ZONE. Enfin la zone sera rechargée dans NSD.

La fonction de vérification n'est pas encore super stable, mais l'objectif est d'avoir toujours un fichier valide pour servir la zone. Vous pouvez revérifier le fichier signé ulterieurement comme ceci :

$ ldns-verify-zone -k /tmp/$ZONE.ds /var/ldnscripts/zones/$ZONE/signed

Si la zone est déclarée valide, il suffit alors de recopier le fichier signé :

# cp /var/ldnscripts/zones/$ZONE/signed /var/nsd/signed/$ZONE
# nsd-control reload $ZONE

Vous pouvez également resigner la zone, mais c'est prendre le risque d'avoir à nouveau le même problème.

Il est vital que le paramètre VALIDITY soit plus élevé que la fréquence à laquelle vous faîtes tourner le script de signature.

J'ai réglé la validité de mes signatures à 7 jours. Le script de signature est lui exécuté tous les cinq jours:

# Cron
# signature dnssec tous les 5 jours à 00:08
8       0 5,10,15,20,25 *       *       /var/ldnscripts/signing.cron

Le script signing.cron contiend juste cette commande :

1
2
3
4
5
#!/bin/sh

for dir in `ls /var/ldnscripts/zones/`; do
    /var/ldnscripts/ldnscript signing $dir
done

Encore une fois, de l'automatisation.

NB : à l'opposé du rollover et des nouvelles clés ZSK, j'ai pensé que l'administrateur local pouvait avoir toutes sortes d'idées et de besoins spéciaux concernant les signatures et leur validité. Raison pour laquelle un script cron est fourni. Vous pouvez tout aussi bien copier-coller son contenu dans /etc/daily.local ou /etc/weekly.local.

init

Init … va créer toute la structure nécessaire pour une zone voulue (dossiers et clés), puis déclencher une signature. C'est rapide, expéditif et ça pose pas de questions. Littéralement.

Si vous voulez partir du bon pied, il faut donc avoir déjà la zone prête à être signée, et son éventuelle configuration particulière, ainsi que NSD. Puis vous faîtes :

# ldnscript init zone.tld

Tadam !

Les clés KSK et ZSK sont générées, la zone est signée et chargée dans NSD. Vous n'avez plus qu'à mettre les DS dans le registre suivant la procédure en vigueur chez votre bureau d'enregistrement.

Check

La commande check a pour mission de s'assurer que votre zone est bien valide aux yeux d'un validateur extérieur. C'est le complément de la validation post-signature indiqué auparavant.

$ ldnscript check 22decembre.eu
According to ns0.ldn-fai.net, 22decembre.eu is secure. Everything is OK.

Elle va, à l'aide de la commande dig, interroger un resolveur DNS pris au hasard dans une liste de huit (Google, OpenDNS, DNS.watch, CensurfriDNS, FDN, ARN et LDN).

Le statut de la zone vous sera alors indiqué (secure, dns valid but insecure, bogus).

Cette commande peut également être utilisée pour vérifier une zone que vous ne controllez pas !

status

Status va énumérer toutes les clés de la zone suivant leur type et leur état, en indiquant aussi leur date de création. Le fichier de zone actuellement en fonction, dans /var/nsd/signed/ sera vérifié et sa date de création affichée.

Ceci permet de s'assurer que pour vous votre zone est valide. C'est donc un complément à la fonction check indiquée au dessus.

FAQ (Comment je fais …)

… pour mettre un nouvel algorithme en place ?

Il faut changer l'algorithme dans le fichier de configuration (global ou local) et soit générer une nouvelle clé manuellement …

# ldnscript zsk zone.tld

… soit attendre le 1er du mois que ça se fasse tout seul.

Idem pour n'importe quel autre paramètre (longueur de clé…).

… pour faire un rollover KSK ?

S'il s'agit juste d'avoir une nouvelle clé maître, lancez simplement :

# ldnscript ksk zone.tld

Si vous voulez autre chose, comme un nouvel algorithme, ou une longueur de clé qui corresponde à quelque chose d'important pour vous (passez à 2048 bits parce qu'on arrive en l'an 2048 ?), modifiez d'abord le fichier de configuration de votre zone, puis générez la clé KSK en question.

La clé sera mise en service lors du prochain rollover. Mettez le DS tout de suite dans le tld.

... pour passer des arguments spéciaux ou temporaires ?

Vous pouvez pas. Ce script est écrit dans l'idée d'une persistance, de manière à dérouler à chaque fois la même procédure barbante.

Si vous avez besoin d'indiquer de nouveaux paramètres, il est probable que vous ayez besoin de les indiquer pour tout le temps. Donc de les écrire.

… pour utiliser une clé spécifique ?

Vous voulez dire, pas l'immédiatement dernière ? Ou vous ne voulez/pouvez pas créer une nouvelle clé maintenant (parce qu'après la limite des 15 jours) ?

Vous pouvez supprimer les clés generated que vous ne souhaitez pas utiliser ou faire un touch sur la clé voulue. Le script sélectionnera normalement la clé qui a été modifiée en dernier.

… pour renouveler une clé en urgence ?

Le cas n'est pas (vraiment) prévu ! Honte sur moi.

Vous pouvez au choix : fouiller la doc de LDNS ou le code du script et trouver comment faire (c'est pas impossible du tout), ou bien réinitialiser à la barbare votre zone (qui consistera dans les faits à effacer au minimum toutes les clés private et generated ainsi que leurs fichiers eux-mêmes, puis lancer le script init décrit au dessus).

... pour installer ça sur un autre système ?

Je n'ai pas écris le script en Bash ou autre shell. Donc il faut soit que vous portiez le script, soit que vous installiez Korn Shell sur votre machine. (Au passage, si vous forkez, pourriez-vous en profiter pour faire remonter vos propositions d'améliorations ? Merci)

Catastrophe, j'ai plus de clé active (private).

Si vous avez une clé en retire, déplacez-là en private. Ces clés restent utilisables jusqu'au dernier moment (Elles n'ont pas de date d'expiration).

Vous pouvez aussi faire ca sur une clé generated si elle est dans la zone depuis suffisamment longtemps.