Café αlpha
  • Home
  • À propos
  • Categories
  • Tags
  • Archives

DNSSEC sur un serveur NSD

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.


Published

Jun 15, 2017

Category

Technique

Tags

  • dns 2
  • dnssec 1
  • nsd 1
  • opendnssec 1

Contact

  • Powered by Pelican. Theme: Elegant