L’hébergeur de mon serveur DNS primaire ayant décidé de nuke la VM pour une obscure raison de facturation au milieu du bordel d’une acquisition de l’entreprise, j’ai décidé d’aller voir ailleurs et de remonter le tout à neuf. Une bonne occasion de remplir la promesse d’aborder DNSSEC, sur un serveur NSD en l’occurrence. J’utilise à cette fin OpenDNSSEC avec le stockage SoftHSM, qui me permet d’automatiser le gros de la nuisance du problème.
Ce type de VM n’a besoin que d’une quantité très limité de ressources, quelques chose comme 256Mo de RAM, 2Go de disque et un CPU très limité. La suite de cet exemple se basera sur une Ubuntu 16.04 sur un VC1S chez Scaleway.
Installation
Pour commencer, installer les paquets élémentaires.
aptitude install nsd opendnssec
Si vous utilisez un OS assez simplifié comme dans mon cas chez Scaleway, rajouter une implémentation de dig et host ne sera pas de trop pour faire du debug correct. Je recommande les versions Knot, qui souffrent de moins de problèmes de sécurité que celles de bind.
aptitude install knot-dnsutils knot-host
Configuration de NSD
Afin de garder les choses bien rangées, commençons par créer un répertoire de zones et un alias pour faciliter l’accès aux zones signées.
cd /etc/nsd
mkdir zones
ln -s /var/lib/opendnssec/signed dnssec
Nous avons déjà le nécessaire pour bien configurer le serveur. La configuration sera maintenant placée dans /etc/nsd/nsd.conf.
key:
name: tsig.lordran.net.
algorithm: hmac-sha256
secret: "XXXXXXXXXXXXXXXXXXXXXXXX"
zone:
name: lordran.net.
zonefile: dnssec/lordran.net.zone
notify: 2001:bc8:31bb:102::1 tsig.lordran.net.
provide-xfr: 2001:bc8:31bb:102::1 tsig.lordran.net.
zone:
name: b.b.1.3.8.c.b.0.1.0.0.2.ip6.arpa.
zonefile: zones/b.b.1.3.8.c.b.0.1.0.0.2.ip6.arpa.zone
notify: 2001:bc8:31bb:102::1 tsig.lordran.net.
provide-xfr: 2001:bc8:31bb:102::1 tsig.lordran.net.
Ce fichier assez minimaliste détaille un moyen de transfert sécurisé par clé pour les zones ainsi que deux zones primaires, une forward et une reverse IPv6. La partie notify et provide-xfr défini un serveur secondaire dont la configuration ressemblerait plus à la suivante.
key:
name: tsig.lordran.net.
algorithm: hmac-sha256
secret: "XXXXXXXXXXXXXXXXXXXXX"
zone:
name: lordran.net.
zonefile: zones/lordran.net.zone
request-xfr: AXFR 2001:bc8:4400:2300::15:807 tsig.lordran.net. # izalith.lordran.net
allow-notify: 2001:bc8:4400:2300::15:807 tsig.lordran.net. # izalith.lordran.net
zone:
name: b.b.1.3.8.c.b.0.1.0.0.2.ip6.arpa.
zonefile: zones/b.b.1.3.8.c.b.0.1.0.0.2.ip6.arpa.zone
request-xfr: AXFR 2001:bc8:4400:2300::15:807 tsig.lordran.net. # izalith.lordran.net
allow-notify: 2001:bc8:4400:2300::15:807 tsig.lordran.net. # izalith.lordran.net
Si vous placez des zones non-signées où il faut, vous aurez déjà la configuration nécessaire pour une infrastructure DNS redondée avec des transferts sécurisés. Par soucis d’exhaustivité, voici une version simplifiée des zones utilisées.
$ORIGIN b.b.1.3.8.c.b.0.1.0.0.2.ip6.arpa.
$TTL 3600
@ IN SOA ns1.lordran.net. hostmaster.lordran.net. (
2017042100 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
3600 ; Minimum
)
; Base
@ IN NS ns1.lordran.net.
@ IN NS ns2.lordran.net.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.2.0 IN PTR kaathe.lordran.net.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.2.0 IN PTR oolacile.lordran.net.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.3.0.2.0 IN PTR artorias.lordran.net.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.4.0.2.0 IN PTR alvina.lordran.net.
$ORIGIN lordran.net.
$TTL 3600
@ IN SOA ns1.lordran.net. hostmaster.lordran.net. (
2017042100
3600 ; Refresh
900 ; Retry
1209600 ; Expire
3600 ; Minimum
)
; Base
@ IN NS ns1.lordran.net.
@ IN NS ns2.lordran.net.
@ IN A 212.129.3.232
@ IN AAAA 2001:bc8:31bb:102::1
; DNS
ns1 IN A 163.172.176.74
ns1 IN AAAA 2001:bc8:4400:2300::15:807
ns2 IN A 212.129.3.232
ns2 IN AAAA 2001:bc8:31bb:102::1
Si les noms de vos serveurs DNS utilisent le domaine qu’ils publient, pensez à ajouter des enregistrements glue à votre registre pour qu’ils fonctionnent.
Configuration d’OpenDNSSEC
NSD ne peut pas lire les zones écrites pas OpenDNSSEC à moins d’être dans le bon groupe.
gpasswd -a nsd opendnssec
Il est aussi important d’initialiser SoftHSM pour éviter des erreurs trop abscons. Le répertoire token manquant en particulier, peut donner des erreurs CKR_GENERAL_ERROR ou “Could not load the object store” pas forcément très explicites.
mkdir /var/lib/softhsm/tokens/
softhsm2-util --init-token --slot 0 --label "OpenDNSSEC"
SoftHSM va demander deux PIN, un utilisateur et un SO. C’est le SO qu’il faudra fournir à OpenDNSSEC.
La configuration OpenDNSSEC s’appuie sur trois fichier en particulier, conf.xml qui contient les réglages généraux, kasp.xml qui détaille les politiques de clé et zonelist.xml qui liste les zones à signer.
Au niveau de la configuration générale, on notera deux gotchas. Le premier est le module SoftHSM, ici à /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so, qui doit correspondre à l’emplacement de votre bibliothèque libsofthsm.so. Le deuxième est le NotifyCommand qui correspond à une commande capable de recharger la zone sur le serveur DNS depuis l’utilisateur OpenDNSSEC, qui sera détaillée plus en avant. N’oubliez pas de remplacer le PIN SoftHSM par votre SO.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<RepositoryList>
<Repository name="SoftHSM">
<Module>/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so</Module>
<TokenLabel>OpenDNSSEC</TokenLabel>
<PIN>0123456789abcd</PIN>
<SkipPublicKey/>
</Repository>
</RepositoryList>
<Common>
<Logging>
<Verbosity>3</Verbosity>
<Syslog><Facility>local0</Facility></Syslog>
</Logging>
<PolicyFile>/etc/opendnssec/kasp.xml</PolicyFile>
<ZoneListFile>/etc/opendnssec/zonelist.xml</ZoneListFile>
</Common>
<Enforcer>
<!--
<Privileges>
<User>opendnssec</User>
<Group>opendnssec</Group>
</Privileges>
-->
<!-- <PidFile>/run/opendnssec/enforcerd.pid</PidFile> -->
<Datastore><SQLite>/var/lib/opendnssec/kasp.db</SQLite></Datastore>
<Interval>PT3600S</Interval>
<!-- <ManualKeyGeneration/> -->
<!-- <RolloverNotification>P14D</RolloverNotification> -->
<!-- the <DelegationSignerSubmitCommand> will get all current
DNSKEYs (as a RRset) on standard input (with optional CKA_ID)
-->
<!-- <DelegationSignerSubmitCommand>/usr/sbin/simple-dnskey-mailer.sh</DelegationSignerSubmitCommand> -->
</Enforcer>
<Signer>
<!--
<Privileges>
<User>opendnssec</User>
<Group>opendnssec</Group>
</Privileges>
-->
<!-- <PidFile>/run/opendnssec/signerd.pid</PidFile> -->
<!-- <SocketFile>/run/opendnssec/engine.sock</SocketFile> -->
<WorkingDirectory>/var/lib/opendnssec/tmp</WorkingDirectory>
<WorkerThreads>4</WorkerThreads>
<!-- <SignerThreads>4</SignerThreads> -->
<!--
<Listener>
<Interface><Port>53</Port></Interface>
</Listener>
-->
<!-- the <NotifyCommmand> will expand the following variables:
%zone the name of the zone that was signed
%zonefile the filename of the signed zone
-->
<NotifyCommand>/etc/opendnssec/scripts/nsd-notify -z %zone</NotifyCommand>
</Signer>
</Configuration>
Les politiques de gestion de clé sont probablement la partie la plus abscons, mais le gros de ces paramètres n’ont pas besoin d’être modifiés. Le système se base sur une clé à durée de vie courte (ZSK) dont le cycle de vie sera entièrement géré par OpenDNSSEC, et une clé à durée de vie longue (KSK) qui fera le lien entre votre TLD et votre domaine, que vous devrez publier sur le registre par vos propres moyens. Si vous commencez à toucher aux diverses durées de vie et délais, gardez en tête qu’un équilibre est nécessaire pour éviter les problèmes. Une durée devrait à la fois ne pas être trop courte pour prendre en compte les délais de vie de cache des résolveurs DNS, et pas non plus trop longue au risque de rendre le système lourd et difficile à entretenir, en particulier lors des roulements de clés.
Deux politiques sont ici détaillées, une politique standard à base de RSA et SHA-256 qui devrait marcher sur toutes les installations, ainsi qu’une politique ECDSA qui demande la version 2.0 de SoftHSM, 2.1 d’OpenDNSSEC ainsi qu’un support de votre registre.
<?xml version="1.0" encoding="UTF-8"?>
<KASP>
<Policy name="rsa-sha256">
<Description>Politique standard utilisant RSA-SHA256</Description>
<Signatures>
<Resign>PT2H</Resign>
<Refresh>P3D</Refresh>
<Validity>
<Default>P14D</Default>
<Denial>P14D</Denial>
</Validity>
<Jitter>PT12H</Jitter>
<InceptionOffset>PT3600S</InceptionOffset>
</Signatures>
<Denial>
<NSEC3>
<!-- <TTL>PT0S</TTL> -->
<!-- <OptOut/> -->
<Resalt>P100D</Resalt>
<Hash>
<Algorithm>1</Algorithm>
<Iterations>5</Iterations>
<Salt length="8"/>
</Hash>
</NSEC3>
</Denial>
<Keys>
<!-- Parameters for both KSK and ZSK -->
<TTL>PT3600S</TTL>
<RetireSafety>PT3600S</RetireSafety>
<PublishSafety>PT3600S</PublishSafety>
<!-- <ShareKeys/> -->
<Purge>P14D</Purge>
<!-- Parameters for KSK only -->
<KSK>
<Algorithm length="2048">8</Algorithm>
<Lifetime>P365D</Lifetime>
<Repository>SoftHSM</Repository>
</KSK>
<!-- Parameters for ZSK only -->
<ZSK>
<Algorithm length="1024">8</Algorithm>
<Lifetime>P90D</Lifetime>
<Repository>SoftHSM</Repository>
<!-- <ManualRollover/> -->
</ZSK>
</Keys>
<Zone>
<PropagationDelay>PT3600S</PropagationDelay>
<SOA>
<TTL>PT3600S</TTL>
<Minimum>PT3600S</Minimum>
<Serial>datecounter</Serial>
</SOA>
</Zone>
<Parent>
<PropagationDelay>PT9999S</PropagationDelay>
<DS>
<TTL>PT3600S</TTL>
</DS>
<SOA>
<TTL>PT172800S</TTL>
<Minimum>PT10800S</Minimum>
</SOA>
</Parent>
</Policy>
<Policy name="ecdsa256">
<Description>Politique standard utilisant ECDSA Curve P-256 avec SHA-256 (nécessite OpenDNSSEC 2.1 ou plus)</Description>
<Signatures>
<Resign>PT2H</Resign>
<Refresh>P3D</Refresh>
<Validity>
<Default>P14D</Default>
<Denial>P14D</Denial>
</Validity>
<Jitter>PT12H</Jitter>
<InceptionOffset>PT3600S</InceptionOffset>
</Signatures>
<Denial>
<NSEC3>
<!-- <TTL>PT0S</TTL> -->
<!-- <OptOut/> -->
<Resalt>P100D</Resalt>
<Hash>
<Algorithm>1</Algorithm>
<Iterations>5</Iterations>
<Salt length="8"/>
</Hash>
</NSEC3>
</Denial>
<Keys>
<!-- Parameters for both KSK and ZSK -->
<TTL>PT3600S</TTL>
<RetireSafety>PT3600S</RetireSafety>
<PublishSafety>PT3600S</PublishSafety>
<!-- <ShareKeys/> -->
<Purge>P14D</Purge>
<!-- Parameters for KSK only -->
<KSK>
<Algorithm length="256">13</Algorithm>
<Lifetime>P365D</Lifetime>
<Repository>SoftHSM</Repository>
</KSK>
<!-- Parameters for ZSK only -->
<ZSK>
<Algorithm length="256">13</Algorithm>
<Lifetime>P90D</Lifetime>
<Repository>SoftHSM</Repository>
<!-- <ManualRollover/> -->
</ZSK>
</Keys>
<Zone>
<PropagationDelay>PT3600S</PropagationDelay>
<SOA>
<TTL>PT3600S</TTL>
<Minimum>PT3600S</Minimum>
<Serial>datecounter</Serial>
</SOA>
</Zone>
<Parent>
<PropagationDelay>PT9999S</PropagationDelay>
<DS>
<TTL>PT3600S</TTL>
</DS>
<SOA>
<TTL>PT172800S</TTL>
<Minimum>PT10800S</Minimum>
</SOA>
</Parent>
</Policy>
</KASP>
Enfin, la liste des zones à signer, relativement explicite. Si vous vous posez la question, il existe d’autres fournisseurs que File, qui permettent par exemple de récupérer et republier une zone par DNS et AXFR
<?xml version="1.0" encoding="UTF-8"?>
<ZoneList>
<Zone name="lordran.net">
<Policy>rsa-sha256</Policy>
<SignerConfiguration>/var/lib/opendnssec/signconf/lordran.net.xml</SignerConfiguration>
<Adapters>
<Input>
<Adapter type="File">/etc/nsd/zones/lordran.net.zone</Adapter>
</Input>
<Output>
<Adapter type="File">/var/lib/opendnssec/signed/lordran.net.zone</Adapter>
</Output>
</Adapters>
</Zone>
</ZoneList>
Une fois que vous êtes satisfait de votre configuration, initialisez-là avec la commande suivante :
ods-ksmutil setup
Puis rechargez les deux démons pour lancer le système.
systemctl restart opendnssec-enforcer
systemctl restart opendnssec-signer
Signer signe les zones avec les clés KSK/ZSK courantes, Enforcer gère la durée de vie des clés. Une clé passe par différents états de sa conception à sa destruction, que je vais détailler pour le cas des KSK.
- publish est l’état initial, a une durée de vie d’une heure pour laisser le temps aux résolveurs de mettre à jour leur cache et d’apprendre l’existence de cette nouvelle clé
- Une heure après, la KSK passe donc en ready et devrait à ce point pouvoir être envoyée sur le registre sans causer de problèmes. OpenDNSSEC n’automatise pas cette étape, qui demande une intervention manuelle de l’opérateur qui devra s’assurer que l’enregistrement DS a bien été publié avant d’entrer une commande pour passer à l’étape suivante.
- active est une clé considérée comme opérationnelle. L’arrivée d’une nouvelle KSK en active provoque la retraite automatique de la précédente
- retire est un état transitoire avant destruction, une fois qu’une nouvelle KSK a été passée en active pour remplacer la précédente
La commande suivante devrait vous donner le statut actuel de toutes vos clés, avec la date de prochaine transition.
ods-ksmutil key list
lordran.net KSK publish 2017-04-20 19:11:43
lordran.net ZSK active 2017-07-19 16:11:43
Interaction avec le serveur DNS
Signer les zones c’est bien, prévenir NSD qu’elles ont changé c’est mieux. Sans automatisation les zones seront vite invalides à mesure que les clés tournent. Cette partie est gérée par la stanza NotifyCommand du fichier conf.xml posté plus haut. Les moyens de contrôle de NSD étant par défaut restreints à root, sauf à jouer avec les restrictions des fichiers /etc/nsd/nsd_control.{key,pem}, il faudra passer par un script setuid root.
Voici un exemple de script utilisable avec la configuration XML donnée précédemment (aucune dépendance).
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import subprocess, argparse, re
RE_DOMAIN = re.compile('^[-\.A-Za-z0-9]+$')
def notify_nsd(zone = None):
if zone and not RE_DOMAIN.match(zone):
zone = None
command = ['/usr/sbin/nsd-control', 'reload']
if zone:
command.append(zone)
subprocess.call(['echo'] + command)
ret_code = subprocess.call(command)
command = ['/usr/sbin/nsd-control', 'notify']
if zone:
command.append(zone)
subprocess.call(['echo'] + command)
ret_code = subprocess.call(command)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-z', '--zone', default=None, help='zone to reload')
args = parser.parse_args()
notify_nsd(zone = args.zone)
Publication d’une clé au registre
La première option pour publier une KSK est un envoi manuel au registre, via leur interface web.
L’information généralement demandée sera le champ DNSKEY avec quelques informations supplémentaires de la politique (algorithme utilisé, entre autres).
ods-ksmutil key export --zone lordran.net --keystate ready --keytype ksk
Certains registres demandent le DS plutôt que DNSKEY, auquel cas la commande sera plutôt la suivante :
ods-ksmutil key export --zone lordran.net --keystate ready --keytype ksk --ds
Vous pouvez fournir le DS SHA-256 ou SHA-1, à votre convenance ou selon les limites de votre registre. Préférez tout de même SHA-256 si possible, la sécurité de SHA-1 commençant à être douteuse ces dernières années.
Vous pouvez vérifier la présence ou pas d’un enregistrement DS sur le TLD avec dig.
dig DS lordran.net @b.gtld-servers.net.
Pour rappel, dig est également capable de fournir les serveurs DNS d’un TLD si vous utilisez autre chose qu’un .net
dig NS net
Une fois que le DS est confirmé, il ne reste plus qu’à en informer OpenDNSSEC (le keytag correspond à l’ID dans le commentaire DNSKEY, ou au premier champ de données du DS)
ods-ksmutil key ds-seen --zone lordran.net --keytag 19759
Cette opération sera à renouveler tous les ans, sauf si vous en avez décidé autrement dans votre politique. Ne stressez toutefois pas trop si vous manquez le coche, OpenDNSSEC ne supprimera jamais une clé avant d’avoir confirmé la publication du successeur, vous gardez donc une certaine liberté sur les délais pour effectuer votre corvée annuelle. Ce système aura toutefois tendance à mal scaler si vous avez un grand nombre de zones.
Automatisation
OpenDNSSEC permet de pousser la fainéantise un peu plus loin en automatisant les ajouts et suppression de clés KSK, pour peu que votre registre propose une API digne de ce nom. Côté OpenDNSSEC, cela passera par les stanzas DelegationSignerSubmitCommand et DelegationSignerRetractCommand auxquelles il faudra fournir un script.
Il est malheureusement difficile de donner un script type étant donné qu’il dépendra complètement de votre registre.
Ressources de validation
Trouvez la source d’un problème avec DNSEC n’est pas toujours très évident, mais vous avez des outils pour vous aider : Zonemaster du côté de l’AFNIC et DNSViz chez Verisign. Même sans problème perçu, n’hésitez pas à faire une validation par acquis de conscience.