Bind : automatiser la mise à jour d'entrées DNS

"to be completed"J'ai eu récemment à configurer un accès vers une machine dont l'adresse IP n'est pas fixe (typiquement derrière une box ADSL dont l'abonnement ne propose pas d'IP fixe). J'avais déjà mis en place un accès similaire il y a quelques années, mais je n'en avais pas fait de billet, voici donc l'occasion.

Plantons un peu le décor

Je suis donc dans la situation suivante : la machine, sous NetBSD, dispose d'un accès Internet derrière une box ADSL fournissant une IPv4 dynamique. Je dispose d'un nom de domaine, d'un serveur DNS public, ainsi que d'un serveur web public. En dehors des éléments, je ne souhaite pas compter sur un service tiers supplémentaire. L'idée est donc la suivante : depuis la machine en question, réussir à obtenir son adresse IP publique de sortie, et aller la donner au serveur DNS pour qu'il mette à jour une entrée afin que la dite machine soit accessible (pour un accès SSH ou HTTPS par exemple).

Étape 1 : connaître son adresse IP publique

Pour cette première étape, j'ai choisi d'utiliser un serveur web existant, qui tourne sous Nginx. Celui-ci me permet d'afficher l'adresse IP du client, sans utiliser de script supplémentaire PHP, Python ou autre. J'ai ajouté la configuration suivante dans mon virtual host :

location /myip {
   default_type text/plain;
   return 200 "$remote_addr";
}

Une fois Nginx relancé, je peux lancer une requête via un navigateur, wget ou curl pour afficher mon adresse IP :

$ curl http://www.example.org/myip
109.XXX.YYY.ZZZ

Étape 2 : mettre à jour une entrée DNS sans les mains

Cette deuxième étape commence par la création, manuelle, d'une nouvelle entrée de type A dans la zone DNS. Je ne détaille cette création, elle est en théorie assez basique pour toute personne qui a déjà monté un serveur DNS. Par contre il va falloir mettre à jour régulièrement cet enregistrement. Pour ne pas avoir à le mettre à jour manuellement, j'ai utilisé nsupdate. Cet outil repose sur la RFC 2136, ce qui a l'avantage d'être ouvert et documenté, et de ne pas être une solution bricolée maison à base de sed dans le fichier de zone en direct.

Pour utiliser nsupdate, il faut commencer par créer une paire de clés TSIG sur le client, et ensuite autoriser la clé publique au niveau du serveur DNS. L'outil dnssec-keygen va nous aider pour la création de clés :

$ dnssec-keygen -a HMAC-SHA256 -b 256 -n HOST dynamic.example.org
Kdynamic.example.org.+163+16284

On notera que l'option -a permet de choisir l'algorithme cryptographique, -b la taille de clé, et l'option -n spécifie le type d'entrée à laquelle se destine cette paire de clés.2 fichiers sont alors produits, dans notre exemples ils se nomment Kdynamic.example.org.+163+16284.key (la clé publique) et Kdynamic.example.org.+163+16284.private (la clé privée). La clé publique a cette tête :

$ cat Kdynamic.example.org.+163+16284.key 
dynamic.example.org. IN KEY 512 3 163 EmvYb14yJA+0qgRmqaMng02cQoCAbekP2ou9M1fNWX4=

Quant à la clé privée :

$ cat Kdynamic.example.org.+163+16284.private 
Private-key-format: v1.3
Algorithm: 163 (HMAC_SHA256)
Key: EmvYb14yJA+0qgRmqaMng02cQoCAbekP2ou9M1fNWX4=
Bits: AAA=
Created: 20181112210734
Publish: 20181112210734
Activate: 20181112210734

Note : je n'ai pas de problème à divulguer cette clé, car je l'ai volontairement générée à des fins d'exemple. Bien entendu, il ne fait pas divulguer sa clé privée ;)

Maintenant, autorisons notre clé publique au niveau du serveur DNS Bind. Cela se situe directement dans le fichier de configuration named.conf, et cela se passe en deux parties. La première consiste à déclarer la clé publique :

key "dynamic.example.org." {
       algorithm HMAC-SHA256;
       secret "EmvYb14yJA+0qgRmqaMng02cQoCAbekP2ou9M1fNWX4=";
};

Attention, il faut bien préciser le même algorithme que lors de la génération de clés.

La deuxième partie consiste à autoriser cette clé publique au niveau de la configuration de la zone DNS sur laquelle je souhaite agir :

zone "example.org" IN {
       type master;
       file "/var/named/master/example.org";
       allow-transfer {10.13.37.53; };
       allow-query { any; };
       update-policy {
               grant dynamic.anotherhomepage.org. name dynamic.anotherhomepage.org. A CNAME TXT;
               grant dynamic2.anotherhomepage.org. name dynamic2.anotherhomepage.org. A CNAME TXT;
       };
};

Il s'agit d'une déclaration relativement classique, mais on notera la présence d'une directive update-policy dans laquelle j'autorise ma clé (définie par le nom lors de la génération par dnssec-keygen) à modifier un enregistrement DNS (définie par name puis son nom) des types décrits après (ici, mon enregistrement peut être de type A, CNAME ou TXT). L'exemple ci-dessus propose même deux enregistrements modifiés par deux clés différentes.

On peut alors utiliser nsupdate. Créons un fichier qui va contenir les données à pousser vers le serveur DNS :

$ cat dnsupdate.txt
server ns0.example.org
zone example.org.
update delete dynamic.example.org.
update add dynamic.example.org. 180 A 10.13.37.92
show
send

Ensuite, lançons nsupdate :

nsupdate -k ./Kdynamic.example.org.+163+16284.private -v ./dnsupdate.txt

Si tout se passe bien, l'enregistrement DNS devrait être à jour. Pour se faciliter les tests, on peut, lors de la création de celui-ci, mettre une valeur volontairement erronée, et constater qu'une fois nsupdate lancé, la valeur est correcte.

Étape 3 : on secoue bien fort

Maintenant qu'on a tous les outils, il ne reste plus qu'à tout englober ensemble dans un script à glisser dans une tâche cron. Voici, dessous, le script que j'ai fait pour l'exemple. Bien entendu, il utilise la méthode "La Rache" et mériterait un peu plus de rigueur dans son développement. Mais c'est un début, fonctionnel et simple à comprendre.

#!/usr/pkg/bin/bash
set -x
curl_bin=$(which curl)
curl_opts="-s"
dig_bin=$(which dig)
nsupdate_bin=$(which nsupdate)
ip_check_service="http://www.example.org/myip"
keyfile="/home/nils/keys/Kdynamic.example.org.+163+16284.private"
current_ip=$(${curl_bin} ${curl_opts} ${ip_check_service})
current_reverse=$(${dig_bin} +short @ns1.fdn.org -x ${current_ip})
previous_cname=$(${dig_bin} +short @ns0.example.org dynamic.example.org)
dns_server=$(dig +short -t A ns0.example.org)

cat > /tmp/majdnscloud.txt << EOF
server ${dns_server}
zone example.org.
update delete dynamic.example.org.
update add dynamic.example.org. 180 CNAME ${current_reverse}
show
send
EOF


nsupdate -k ${keyfile} -v /tmp/majdnscloud.txt
rm -f /tmp/majdnscloud.txt

Autres possibilités ?

Il se peut qu'on ne dispose pas de ressource pour installer un serveur qui donnerait notre IP publique de sortie, il est alors possible d'utiliser un service tiers. J'en utilise occasionnellement deux : What's My IP et IP chicken.

Pour ce qui est de la mise à jour automatisée d'un enregistrement DNS, selon le registrar, il est possible que celui-ci le propose via une API, comme Gandi LiveDNS par exemple.

Vous avez aimé cet article ? Alors partagez-le sur les réseaux sociaux !

Crédit photo : Jake Givens - Busy freeway traffic at night.

Commentaires

Le 08/07/2019 22:37 par cmic

Hello Cool. Je me souviens d'avoir écrit la même chose (ou presque) en Perl pour mettre ma zone à jour avec l'ajout ou la suppression d'un PC ou d'un serveur ; avec maj du reverse également. cmic, Sysadmin à la retraite...

Le 09/07/2019 19:53 par user

grant dynamic.anotherhomepage.org. name dynamic.anotherhomepage.org. A CNAME TXT; grant dynamic2.anotherhomepage.org. name dynamic2.anotherhomepage.org. A CNAME TXT;

Ça ne devrait pas être « grant dynamic.example.org. » vu le nom des clés générées au-dessus ?

Merci pour le tuto :)

Le 09/07/2019 22:05 par Nils

@user : en fait non, comme j'indique avec maladresse dans mon billet :

L'exemple ci-dessus propose même deux enregistrements modifiés par deux clés différentes.

Je cherchais à montrer qu'en mettant une deuxième clé, on pourrait avoir pour la même zone un deuxième enregistrement dynamique, mais je n'ai pas pris la peine de dupliquer toutes les autres parties. Pardon pour la confusion !