---
categories: web system
title: Howto BIND
...

* Documentation : <https://www.isc.org/downloads/bind/doc/bind-9-10/>
* DNS et BIND : <http://www.zytrax.com/books/dns/>
* Rôle Ansible : <https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/bind>

[BIND (Berkeley Internet Name Daemon)](https://www.isc.org/downloads/bind/) est le serveur [DNS](https://fr.wikipedia.org/wiki/Domain_Name_System) historique écrit au début des années 1980, c'est encore le plus utilisé sur Internet. Il est développé par l'[ISC](https://www.isc.org/) qui développe aussi [ISC DHCP](HowtoDHCP). BIND peut être à la fois serveur **récursif** (résoudre n'importe quel enregistrement DNS pour certaines machines) ou serveur **faisant autorité** (renvoyer au monde entier les enregistrements DNS d'un nom de domaine spécifique).

## Installation

~~~
# apt install bind9

$ /usr/sbin/named -V
BIND 9.18.19-1~deb12u1-Debian (Extended Support Version) <id:>
running on Linux x86_64 6.1.0-13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.55-1 (2023-09-29)
built by make with  '--build=x86_64-linux-gnu' '--prefix=/usr' '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' '--disable-option-checking' '--disable-silent-rules' '--libdir=${prefix}/lib/x86_64-linux-gnu' '--runstatedir=/run' '--disable-maintainer-mode' '--disable-dependency-tracking' '--libdir=/usr/lib/x86_64-linux-gnu' '--sysconfdir=/etc/bind' '--with-python=python3' '--localstatedir=/' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--disable-static' '--with-gost=no' '--with-openssl=/usr' '--with-gssapi=yes' '--with-libidn2' '--with-json-c' '--with-lmdb=/usr' '--with-gnu-ld' '--with-maxminddb' '--with-atf=no' '--enable-ipv6' '--enable-rrl' '--enable-filter-aaaa' '--disable-native-pkcs11' '--enable-dnstap' 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -ffile-prefix-map=/build/reproducible-path/bind9-9.18.19=. -fstack-protector-strong -Wformat -Werror=format-security -fno-strict-aliasing -fno-delete-null-pointer-checks -DNO_VERSION_DATE -DDIG_SIGCHASE' 'LDFLAGS=-Wl,-z,relro -Wl,-z,now' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'
compiled by GCC 12.2.0
compiled with OpenSSL version: OpenSSL 3.0.10 1 Aug 2023
linked to OpenSSL version: OpenSSL 3.0.11 19 Sep 2023
compiled with libuv version: 1.44.2
linked to libuv version: 1.44.2
compiled with libnghttp2 version: 1.52.0
linked to libnghttp2 version: 1.52.0
compiled with libxml2 version: 2.9.14
linked to libxml2 version: 20914
compiled with json-c version: 0.16
linked to json-c version: 0.16
compiled with zlib version: 1.2.13
linked to zlib version: 1.2.13
linked to maxminddb version: 1.7.1
compiled with protobuf-c version: 1.4.1
linked to protobuf-c version: 1.4.1
threads support is enabled
DNSSEC algorithms: RSASHA1 NSEC3RSASHA1 RSASHA256 RSASHA512 ECDSAP256SHA256 ECDSAP384SHA384 ED25519 ED448
DS algorithms: SHA-1 SHA-256 SHA-384
HMAC algorithms: HMAC-MD5 HMAC-SHA1 HMAC-SHA224 HMAC-SHA256 HMAC-SHA384 HMAC-SHA512
TKEY mode 2 support (Diffie-Hellman): yes
TKEY mode 3 support (GSS-API): yes

default paths:
  named configuration:  /etc/bind/named.conf
  rndc configuration:   /etc/bind/rndc.conf
  DNSSEC root key:      /etc/bind/bind.keys
  nsupdate session key: //run/named/session.key
  named PID file:       //run/named/named.pid
  named lock file:      //run/named/named.lock
  geoip-directory:      /usr/share/GeoIP
~~~

Sous Debian 8, l'unité [systemd](HowtoSystemd) ne gère pas les options dans `/etc/default/bind9`, il faut corriger l'unité en copiant :

~~~
# cp -a /lib/systemd/system/bind9.service /etc/systemd/system/
~~~

et ajuster la section _[Service]_ :

~~~
EnvironmentFile=-/etc/default/bind9
ExecStart=/usr/sbin/named -f $OPTIONS
~~~

puis :

~~~
# systemctl daemon-reload
~~~

## Configuration

Les options de BIND sont dans `/etc/default/named` (`/etc/default/bind9` avant Bullseye).

Fichiers de configuration :

~~~
/etc/bind
├── bind.keys
├── db.0
├── db.127
├── db.255
├── db.empty
├── db.local
├── named.conf
├── named.conf.default-zones
├── named.conf.local
├── named.conf.options
├── rndc.key
└── zones.rfc1918
~~~

### serveur DNS récursif

Lorsque BIND est utilisé en serveur DNS récursif pour une ou plusieurs machines, on conseille d'activer la résolution DNS inverse pour les zones RFC1918 dans le fichier `/etc/bind/named.conf.local` (c'est important car de nombreux outils vérifient les [reverses DNS](#Reverse-DNS)) :

~~~
include "/etc/bind/zones.rfc1918";
~~~

Puis ajuster les options via `/etc/bind/named.conf.options` :

~~~
options {
    directory "/var/cache/bind";
    version "Bingo";
    auth-nxdomain no;
    listen-on-v6 { ::1; };
    listen-on { 127.0.0.1; };
    allow-recursion { ::1; 127.0.0.1; };
};

logging {
        category default { default_file; };
        channel default_file {
                file "/var/log/bind.log";
                severity info;
        };
};
~~~

> *Note* : dans l'exemple, BIND n'écoute que pour la machine locale. Si l'on veut l'interroger avec d'autres machines, on ajustera les options `listen-*` et `allow-recursion` (on pourra notamment utiliser [l'ACL `localnets`](#acl)


### serveur DNS faisant autorité

Lorsque BIND est utilisé en serveur DNS faisant autorité, on conseille de le sécuriser en [l'enfermant dans un chroot](#chroot) (c'est notamment indispensable pour la configuration ci-dessous qui va rotater `/var/log/bind_queries.log` et qui échouera donc si Bind n'est chrooté comme conseillé).

On va ensuite configurer via le fichier `/etc/bind/named.conf.options` :

~~~
acl "foo" {
    ::ffff:192.0.2.21; 192.0.2.21;
    2001:db8::21;
};

options {
      directory "/var/cache/bind";
      version "Bingo";
      auth-nxdomain no;
      masterfile-format text;
      statistics-file "/var/run/named.stats";

      listen-on-v6 { any; };
      listen-on { any; };

      allow-query { localhost; };
      allow-recursion { localhost; };
      allow-transfer { localhost; };
};

logging {
    category default { default_file; };
    category queries { query_logging; };

    channel default_file {
        file "/var/log/bind.log";
        severity info;
    };
    channel query_logging {
        file "/var/log/bind_queries.log" versions 2 size 128M;
        print-category yes;
        print-severity yes;
        print-time yes;
    };
};
~~~

On peut ensuite ajouter ses domaines pour lesquels BIND fait autorité dans le fichier `/etc/bind/named.conf.local`, par exemple pour _example.com_ avec un serveur replica dans l'ACL _foo_ :

~~~
zone "example.com" { 
        type master; 
        file "/etc/bind/db.example.com";
        allow-query { any; };
        allow-transfer { "foo"; };
};
~~~

Puis enfin les fichiers de zone, exemple avec un `/etc/bind/db.example.com` simple :

~~~
$TTL 1800
@          IN      SOA ns1.example.net. dnsmaster.example.com. (
                   2017072107 ; serial
                   2h         ; rafraichissement replica->master
                   1h         ; en cas d'echec du refraichissement, nouvel essai
                   5w         ; expiration des enregistrements en cache par les serveurs replica
                   10m )      ; TTL negatif

           A 192.0.2.80

           NS ns1.example.net.
           NS ns2.example.org.

           MX 0    .
           TXT "v=spf1 -all"

www        CNAME @
ftp        CNAME ftp.example.net.
~~~

> *Note* : on s'assurera que les fichiers de zone sont lisibles par l'utilisateur _bind_ en faisant `chown bind:bind /etc/bind/db.example.com`

### Options de configuration

* `allow-query` : spécifie les adresses autorisées à faire des requêtes DNS. Par défaut, toutes les adresses sont autorisées
* `allow-transfer` : spécifie les adresses des serveurs replica ou clefs [TSIGs](/Glossaire.md#tsig) autorisées à faire des requêtes AXFR (transfert de zone). Par défaut, toutes les adresses sont autorisées, il est impératif de restreindre les autorisations.
* `allow-recursion` : spécifie les adresses autorisées à faire des requêtes DNS récursives. À restreindre impérativement également.
* `allow-update` : spécifie les adresses ou clefs [TSIGs](/Glossaire.md#tsig) autorisées à mettre à jour dynamiquement des informations dans leur zone. Par défaut, aucune adresse n'est autorisée.
* `update-policy` : spécifie finnement les droit de modification des entrées pour les adresses ou clefs [TSIGs](/Glossaire.md#tsig) autorisées via une liste d'ACL, **Attention** : on ne peut pas définir à la fois `allow-update` et `update-policy`. voir [Configuration update-policy](/HowtoBind#configuration-update-policy)

## chroot

Pour enfermer BIND dans une prison _chroot_ nous utilisons le script [chroot-bind.sh](https://gitea.evolix.org/evolix/chroot-bind/src/branch/master/chroot-bind.sh) :

~~~
# cd /root
# wget https://gitea.evolix.org/evolix/chroot-bind/raw/branch/master/chroot-bind.sh
# sh chroot-bind.sh
~~~

Puis ajoutez `-t /var/chroot-bind` dans la variable OPTIONS du fichier `/etc/default/named` (`/etc/default/bind9` avant Bullseye) :

~~~
RESOLVCONF=no
OPTIONS=" -u bind -t /var/chroot-bind"
~~~

### AppArmor depuis Buster (Debian 10)

AppArmor protège par défaut les chemins utilisés par la version distribuée par Debian, il faut ajouter les chemins effectivement utilisés dans le chroot.

~~~ { .sh }
# cat /etc/apparmor.d/local/usr.sbin.named
/var/chroot-bind/etc/bind/** r,
/var/chroot-bind/var/** rw,
/var/chroot-bind/dev/** rw,
/var/chroot-bind/run/** rw,
/var/chroot-bind/usr/** r,
~~~

Il faut relancer apparmor et bind9 après ces ajouts.

~~~ { .sh }
# systemctl daemon-reload
# systemctl apparmor restart
# systemctl apparmor bind9
~~~

Sinon vous aurez des erreurs `open: /etc/bind/named.conf: permission denied` et `audit: type=1400 audit(1689581621.287:26): apparmor="DENIED" operation="open" profile="/usr/sbin/named" name="/var/chroot-bind/etc/bind/named.conf" pid=3205399 comm="named" requested_mask="r" denied_mask="r" fsuid=115 ouid=115` dans `/var/log/syslog`.

### bind mount à partir de Bookworm (Debian 12)

Depuis Bookworm, des points de montage sont nécessaires pour un fonctionnement normal de systemd et journald (sinon l’unité systemd ne renvoie jamais de signal après un redémarrage correct).

`/etc/fstab` doit contenir les lignes suivantes.

~~~
/run/systemd/journal/socket /var/chroot-bind/run/systemd/journal/socket none    bind    0   0
/run/systemd/journal/stdout /var/chroot-bind/run/systemd/journal/stdout none    bind    0   0
/run/systemd/notify         /var/chroot-bind/run/systemd/notify         none    bind    0   0
~~~

Puis les points de montages doivent être créés et montés.

~~~ { .sh }
# mkdir -p /var/chroot-bind/run/systemd/journal
# touch /var/chroot-bind/run/systemd/{journal/s{ocket,tdout},notify}
# chown -R bind: /var/chroot-bind/run/
# systemctl daemon-reload
# mount -a
~~~

### Relancer bind

Vous devez alors relancer BIND :

~~~
# systemctl restart bind9
~~~

À chaque mise à jour du paquet _bind9_ ou de l'une de ses dépendances (_libc6_, _libcap2_, _libssl_, etc.) vous devrez relancer le script _chroot-bind.sh_ et BIND ensuite.


### Problèmes de permissions avec chroot

* Si erreur : `named: chroot(): Permission denied`, vérifier que `/var/chroot-bind` est en `750`.
* Si `/var/log/bind_queries.log` n'existe pas, il faut créer le lien symbolique : `ln -s /var/chroot-bind/var/log/bind_queries.log /var/log/bind_queries.log`


## Rotation des logs

On utilise [logrotate](HowtoLogrotate) pour la rotation des logs.

Voici le fichier `/etc/logrotate.d/bind9` que l'on utilise pour un serveur récursif :

~~~
/var/log/bind.log {
    weekly
    missingok
    rotate 8
    create 640 bind bind
    sharedscripts
    postrotate
        rndc reload > /dev/null
    endscript
}
~~~

Pour un serveur faisant autorité, on doit préciser le path des fichiers dans le _chroot_ :

~~~
/var/chroot-bind/var/log/bind.log {
    weekly
    missingok
    rotate 52
    create 640 bind bind
    sharedscripts
    postrotate
        rndc reload > /dev/null
    endscript
}
~~~

> *Note* : la rotation du fichier `/var/chroot-bind/var/log/bind_queries.log` est assurée par BIND sous réserve d'avoir bien précisé l'option `versions` dans les paramètres de logging

Il est aussi possible de faire la rotation du fichier de statistique (`/var/run/named.stats` ou `/var/chroot-bind/var/run/named.stats` avec chroot) avec logrotate:

~~~
/var/run/named/named.stats
{
        rotate 7
        daily
        missingok
        notifempty
        delaycompress
        compress
        su bind bind
}
~~~

La rotation du fichier de statistiques n'affecte ni Munin, ni BIND.

## ACL

<http://www.zytrax.com/books/dns/ch7/address_match_list.html>

Au sein des différentes directives `allow-*` on peut utiliser des ACLs.

Par défaut, les ACLs suivantes sont prédéfinies :

* `none` : aucune adresse IP
* `any` : toutes les adresses IP
* `localhost` : toutes les adresses IP de la machine (127.0.0.1 mais aussi les adresses configurées sur les différentes interfaces réseau)
* `localnets` : tous les sous-réseaux dans lesquels la machine possède une adresse IP

On peut également définir ses propres ACLs :

~~~
acl "bar" {
    ::ffff:192.0.2.21; 192.0.2.21;
    ::ffff:192.0.2.42; 192.0.2.42;
    2001:db8::21; 2001:db8::42;
};
~~~

## DNS primaires et secondaires

Il est possible de répliquer la configuration des zones DNS d'un serveur DNS primaire vers plusieurs secondaires.

Cela n'empêche pas les secondaires d'être primaires pour d'autres zones.

Si l'on reprend le cas de la zone suivante sur le primaire :

~~~
zone "example.com" {
        type master;
        file "/etc/bind/db.example.com";
        allow-query { any; };
        allow-transfer { <SLAVE1_IP>; <SLAVE2_IP>; ... | "<ACL1>"; "<ACL2>"; ... };
};
~~~

Sur le secondaire, on spécifiera l'adresse IP du primaire ainsi qu'un fichier pour sauvegarder la zone en local :

~~~
zone "example.com" {
        type slave;
        file "/etc/bind/bak.example.com";
        allow-query { any; };
        masters { <MASTER_IP>; };
};
~~~

Notes : 

* Attention aux IPv4 / IPv6.
* Si on édite manuellement la configuration du master (plutôt que d'utiliser le DNS dynamique avec `nsupdate`), il faut absolument mettre `request-ixfr no;` dans la configuration de Bind sur les secondaires (dans les options générales, ou dans un bloc `server`).
  * Pour en savoir plus, voir [la documentation de Bind](https://bind9.readthedocs.io/en/v9.16.20/advanced.html#incremental-zone-transfers-ixfr).
* Pour avoir le fichier de zone en clair sur le serveur secondaire (ce qui est pratique notamment en cas d'incident avec le serveur primaire), il faut s'assurer d'avoir l'option `masterfile-format text;` dans la configuration de Bind.


### Debugger

Pour savoir si le primaire renvoie bien la zone DNS à jour, à partir du serveur DNS secondaire, faire une requête de [transfert de zone incrémental](https://fr.wikipedia.org/wiki/Transfert_de_zone_DNS) avec `dig` :

~~~
dns-secondaire$ dig -b <IP_OUT> <DOMAIN> IXFR=1 @<PRIMARY_DNS_IP>
~~~

Options :

* `-b <IP_OUT>` : si on a plusieurs IPs de sortie, force bind à faire sa requête à partir de `IP_OUT`.
* `IXFR=1` : récupère les changements de la zone depuis le serial 1.

## Mise à jour dynamique

La mise à jour dynamique permet de mettre à jour une zone avec des demandes de mise à jour `Dynamic Updates` envoyés par le réseau au server autoritaire pour ajouter ou supprimer des champs ( voir RFC2136 )

* Il n'y a plus besoin de se connecter au serveur autoritaire pour changer le fichier de zone
* La syntaxe d'une demande est vérifiée avant d'être envoyé pour éviter d'ajouter des entrées incorrectes. 
* Le SOA est incrémenté automatiquement. 
* Dans une zone DNSSEC, le changement est immédiatement signé.
* On peut facilement scripter ces mises à jours ( certbot par exemple )
* **Important** : Les directives `$INCLUDE` et `$GENERATE` d'un fichier de zone ne pourront plus être utilisé. Pour conserver cette fonctionnalité, on recommande d'activer la mise à jour uniquement sur des sous domaines/zone d'une zone qui utilise ces directives (ces sous-zones seront alors incluses).

> **Attention** : lorsqu'on active le `dynamic-update` sur une zone, il devient difficile d'éditer le fichier de zone à la main, voir [màj manuelle](#mise-à-jour-manuelle-de-la-zone).

### Activer la mise à jour dynamique sur une zone

Pour faire la mise à jour dynamique, Bind génère un fichier de journal `.jnl` binaire du même nom que la zone, contenant l'historique des modifications apportées à la zone.
L'historique est 

> **Note** : la synchronisation avec le fichier de zone n'est pas faite en temps réel, cela se fait toutes les 15 minutes en général, on peut forcer la synchronisation avec `$ rndc sync [nom_zone]`.

Pour éviter de donner à Bind le droit d'éditer tous les fichiers dans `/etc/bind`, il est préférable de placer les fichiers de zones mise à jour dynamique dans un dossier séparer ou bind à l'accès en écriture, par exemple :

~~~{.bash}
$ mkdir -p /etc/bind/dynamic-zones
$ chown -R bind:bind /etc/bind/dynamic-zones
~~~

> **Attention** : la zone ne doit pas contenir de directives telles que `$INCLUDE` et `$GENERATE` ... celles-ci seront perdues à la synchronisation avec le fichier journal

On peut alors mettre en place le fichier de zone dans ce dossier...

~~~{.bash}
# copier ou créer un fichier de zone avec les bon droits
$ touch /etc/bind/dynamic-zones/db.example.com
$ chown bind:bind /etc/bind/dynamic-zones/db.example.com
~~~

Puis activer la mise à jour dynamique dans sa configuration :

~~~
zone "example.com" in {
        type                    master;
        file                    "/etc/bind/dynamic-zones/db.example.com";
       allow-update { key "custom-tsig"; localhost ;};
       allow-transfer { key "secondary-tsig"; 1.2.3.4 ; localhost ; };
//     update-policy           {
//       grant custom-tsig  name _acme-challenge.example.com. txt;
// };
//     serial-update-method ( date | increment | unixtime );
};
~~~

* `allow-update` permet d'autoriser la mise à jour de toute la zone avec des IPs ou clefs TSIGs tandis que `update-policy` permet de contrôler finement quels champs peuvent être mis à jour, voir [configuration update-policy](#configuration-update-policy)
* `allow-transfer` pour permettre de transférer la zone, utilisé pour que le client puisse aussi récupérer la zone
* `serial-update-method` permet de définir le mécanisme de mise à jour du serial SOA (incrément par défaut).

> Voir [mise en place d'une clef tsig](#mise-en-place-de-clefs-tsigs) si besoin

On peut ensuite tester le fonctionnement en jouant nsupdate ou nsvi pour ajouter ou supprimer une entrée.

Si le nsupdate fonctionne, on peut alors vérifier avec un dig normal ou axfr ( par exemple `dig -t axfr example.com @localhost` sur le server dns) ou encore mieux, en [controlant le journal](#contrôler-le-journal-dune-zone).

### client nsupdate

nsupdate est utilitaire en ligne de commande interactif fournit le paquet de bind `bind9-dnsutils` sur Debian.

Celui-ci permet d'envoyer des demandes de mise à jour à un serveur dns, avec ou sans TSIGs :

~~~{.bash}
# nsupdate [-y [hmac:]keyname:secret]
$ nsupdate -y hmac-sha512:happydomain:"3JtiVQEBFXtQeU/3PorKpCV7jM1bZEEzkdD1zb9emvwaBZFSgYxOXV41OdSeeHb5dcud+SvNo47jEf5yRwfSkQ=="
> server <adresse_server> [port]
> zone example.com
> update add www.example.com.      172800 IN A 192.168.254.7
> send
> 
~~~

> Une réponse vide après send indique que la mise à jour à eu lieu. Sinon un code d'erreur sera affiché.

Le manuel contient plusieurs examples.

### nsdiff

Documentation : <https://dotat.at/prog/nsdiff/>

nsdiff est outil permettant de faciliter l'utilisation de nsupdate avec différents utilitaires:

* nsdiff : comme son nom l'indique, permet de voir la diff entre une ancienne et une nouvelle vertsion de la zone, sa sortie peut directement etre fournie à nsupdate : `nsdiff | nsupdate`
* nspatch : joue `nsdiff | nsupdate` et n'affiche rien s'il n'y a pas d'erreur, utile dans un CRON
* nsvi : fait un transfert de zone, ouvre la zone dans vi (ou un autre editeur) et envoie les mise à jour avec `nsdiff | nsupdate`

> **Remarque** : les enregistrements dnssec sont retirés avant de passer la zone à l'éditeur et il n'y a pas besoin de s'occuper du SOA

Exemples d'utilisations (WIP) :

~~~{.bash}
# editer une zone dynamique sur le serveur dns ... (avec localhost dans allow-update)
# nsvi example.com
# editer une zone dynamique avec la clef tsig associée
~~~

### Contrôler le journal d'une zone

On peut vérifier le contenu du journal

~~~{.bash}
# voir les changement dans le fichier de journal .jnl
$ named-journalprint [fichier_zone_jnl]
# si il y a des changement, on peut les synchroniser et vider le journal
$ rndc sync -clean example.com
~~~

### Mise à jour manuelle de la zone

Il est possible de mettre à jour le fichier de zone directement, mais ce n'est pas recommandé, car on va devoir temporairement désactiver la mise à jour dynamique "freeze".

Avant cela, il est préférable de vérifier l'état du journal et de la vider ( voir [contrôler le journal](#contrôler-le-journal-dune-zone) )

~~~{.bash}
# désactiver la mise à jour dynamique
$ rndc freeze [nom_zone]
# Modification manuelle de la zone avec vim ou autre
$ named-checkzone <nom_zone> <fichier_de_zone>
$ rndc unfreeze [nom_zone]
$ rndc reload [nom_zone]
~~~

Comme cela est compliqué et risqué, il est préférable d'utiliser un outil comme nsdiff avec nsvi qui va nous permettre d'éditer la zone comme si on l'édite à la main, mais va envoyer les changements par demande de mise à jour, voir [nsdiff](#nsdiff).

### Mise en place de clefs TSIGs

On peut utiliser nsupdate, ou n'importe quel client, pour mettre à jour la zone en s'authentifiant avec une clef [TSIG](/Glossaire.md#tsig).
Pour mettre celle-ci en place, il faut ...

Générer une clef TSIG avec tsig-keygen si disponible ou avec dnssec-keygen sinon

~~~{.bash}
$ tsig-keygen -a hmac-sha512 nomclef
$ dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST nomclef
~~~

Ajouter celle-ci à la configuration de bind...

~~~{.ini}
$ vim /etc/bind/named.conf.tsigkeys

key "happydomain" {
  algorithm HMAC-SHA512;
  secret "3JtiVQEBFXtQeU/3PorKpCV7jM1bZEEzkdD1zb9emvwaBZFSgYxOXV41OdSeeHb5dcud+SvNo47jEf5yRwfSkQ==";
};
~~~

> Ne pas oublier d'inclure named.conf.tsigkeys dans `/etc/bind/named.conf`

On peut maintenant recharger la configuration et vérifier que notre clef est bien présente:

~~~{.bash}
$ named-checkconf /etc/bind/named.conf && rndc reload
$ rndc tsig-list
~~~

## DNSSEC

### Ajouter la gestion DNSSEC

Créer le répertoire `/etc/bind/dnssec/db.$domain` (qui servira aussi à conserver les clefs et fichiers de zones dynamiques) et y déplacer le fichier de zone (un lien symbolique depuis son emplacement précédent permet de continuer à pouvoir l’éditer comme avant). Prendre garde à ce que tous les fichiers et répertoires appartiennent bien à bind.

~~~
# sudo -u bind mkdir -p /etc/bind/dnssec/$domain
# mv /etc/bind/db.$domain /etc/bind/dnssec/$domain/
# ln -s dnssec/$domain/db.$domain /etc/bind/
~~~

Mettre en place une paire de clefs de zone (ZSK) et de signature (KSK)

~~~
# cd /etc/bind/dnssec/$domain
# sudo -u bind dnssec-keygen -a ECDSAP256SHA256 -n ZONE $domain
# sudo -u bind dnssec-keygen -a ECDSAP256SHA256 -n ZONE -f KSK $domain
~~~

Modifier le fichier de zone dans `/etc/bind/named.conf.local` pour ajouter les trois dernières options suivantes (et modifier le chemin vers le fichier de zone).

~~~
zone "$domain" {
        type master;
        file "/etc/bind/dnssec/$domain/db.$domain";
        allow-query { any; };
        allow-transfer { "foo"; };
        inline-signing yes;
        auto-dnssec maintain;
        key-directory "/etc/bind/dnssec/$domain";
};
~~~

Puis recharger la configuration de bind et la zone sera automatiquement signé.

~~~
# systemctl reload bind9
~~~

NSEC3 peut être ajouté à la zone signée.

~~~
# rndc signing -nsec3param 1 0 10 `cat /dev/urandom | tr -dc 'A-F0-9' | fold -w 8 | head -n 1` $domain
~~~

### Modification de la zone

Attention le serial de la zone signée et du fichier original non signé peuvent différer (mise à jour dynamique).

Deux commandes pour savoir où en est le serial :

~~~
$ host -t soa $domain 127.0.0.1
# named-compilezone -j -i none -f raw -F text -o - $domain $domain.signed
~~~

Une fois le bon serial récupéré on peut modifier la zone en incrémentant le serial trouvé.

~~~
# Modifier le domaine example.org
domain=example.org
# Vérification du SOA pour le mettre à jour dans le fichier.
host -t soa $domain
# Arrêt (gel) de la mise à jour dynamique
rndc freeze $domain
# Mise à jour de la zone en pensant au SOA.
vim /etc/bind/db.$domain
# Vérification de la configuration.
named-checkzone $domain /etc/bind/db.$domain
named-checkconf
# Relance de la mise à jour dynamique
rndc thaw $domain
~~~

La zone sera automatiquement resigné.

### Rotation de la ZSK

On génère une nouvelle clé comme vu [plus haut](#ajouter-la-gestion-dnssec).

Le temps d'inactivation de l'ancienne clé ainsi que la date de suppression doit être indiqué.

~~~
# La clé doit être retirée dans 2 jours
dnssec-settime -I +2d OLD_ZSK.key

# La clé doit être supprimée dans 6 jours
dnssec-settime -D +6d Kexample.tld.+008+25266.key
~~~

Bind fera tout seul la prébublication et le rollover.

Les 2 jours et les 6 jours sont à modifier en fonction du ttl de la zone, il faut à minima :

* 2 ttl avant que la OLD_ZSK soit inactivé ;
* durant ces 2 ttl, il y a 1 ttl où la OLD_ZSK est activée et la NEW_ZSK est publiée ;
* à la fin des 2 ttl la NEW_ZSK est activée et la OLD_ZSK peut être supprimée.

## Reverse DNS

Un reverse DNS est un nom de domaine associé à une adresse IP. Le nom de cet enregistrement est *PTR*. Exemples :

~~~
$ dig -x 31.170.8.43

;; QUESTION SECTION:
;43.8.170.31.in-addr.arpa.      IN      PTR

;; ANSWER SECTION:
43.8.170.31.in-addr.arpa. 43200 IN      PTR     hosting.evolix.net.

$ dig -x 2a01:9500::3

;; QUESTION SECTION:
;3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.5.9.1.0.a.2.ip6.arpa. IN PTR

;; ANSWER SECTION:
3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.5.9.1.0.a.2.ip6.arpa. 43200 IN PTR forge.evolix.net.
~~~

Cela fonctionne avec un nom de domaine "virtuel" : `.in-addr.arpa` en IPv4 et `.ip6.arpa` en IPv6 !

Il est important de configurer les reverses DNS, car ils sont souvent vérifiés par les serveurs SMTP, et certains logiciels les vérifient également (Postfix, MySQL, CUPS, SSH, etc.). Sur un réseau avec des adresses privées, il également important de renvoyer au minimum une réponse DNS _NXDOMAIN_ ce qui peut être fait facilement grâce au fichier `/etc/bind/zones.rfc1918`.

À noter que [les DNS de l'IANA répondent tout de même pour ces demandes... mais sans garantie de bon fonctionnement !](http://www.iana.org/abuse/answers)


## Monitoring

### dnstop

L'outil **dnstop** permet de surveiller en direct l'activité DNS en analysant les paquets réseau qui circulent :

~~~
# apt install dnstop
# dnstop -l3 eth0

 s - Sources list
 d - Destinations list
 t - Query types
 o - Opcodes
 r - Rcodes
 1 - 1st level Query Names      ! - with Sources
 2 - 2nd level Query Names      @ - with Sources
 3 - 3rd level Query Names      # - with Sources
 4 - 4th level Query Names      $ - with Sources
 5 - 5th level Query Names      % - with Sources
 6 - 6th level Query Names      ^ - with Sources
 7 - 7th level Query Names      & - with Sources
 8 - 8th level Query Names      * - with Sources
 9 - 9th level Query Names      ( - with Sources
^R - Reset counters
^X - Exit
~~~

### Munin

Pour activer les plugins Munin pour BIND :

~~~
# cd /etc/munin/plugins
# ln -s /usr/share/munin/plugins/bind9_rndc
# ln -s /usr/share/munin/plugins/bind9
~~~

Le plugin *bind9_rndc* s'appuie sur le fichier `/var/run/named.stats` (option _statistics-file_ de BIND) et *bind9* s'appuie un fichier de logs (channel *query_logging* dans les logs BIND). Il donc s'assurer d'avoir bien configuré ces deux fichiers puis on indique à Munin via `/etc/munin/plugin-conf.d/bind9` :

~~~
[bind*]
user root
env.logfile /var/chroot-bind/var/log/bind_queries.log
env.querystats /var/chroot-bind/var/run/named.stats
env.MUNIN_PLUGSTATE /var/lib/munin
timeout 120
~~~

[comment]: <> (*pour vim)

### Log2mail

Pour être alerté en cas de fichiers de zone incorrect, on peut rajouter ceci dans la conf de [Log2mail](HowtoLog2mail) :

~~~
file = /var/chroot-bind/var/log/bind.log
  pattern = "not loaded due to errors"
  mailto = alert@example.com

file = /var/log/syslog
  pattern = "fatal error"
  mailto = alert@example.com

  pattern = "loading configuration: failure"
  mailto = alert@example.com
~~~


## géolocalisation

BIND peut répondre une réponse différente selon l'adresse IP qui l'interroge, on peut faire cela de deux façons :


* Depuis Buster (Debian 10) le [patch geoip](http://code.google.com/p/bind-geoip/) a été intégré a Bind. Le principe est de faire une recherche suivant un arbre binaire (binary search tree) sur la base de données de MaxMind. Cela n'impacte pas les temps de réponse mais nécessite de maintenir un Bind patché avant Buster ;
* en se basant sur le système de vue de Bind. Le principe est de récupérer les plages d'adresses par pays auprès de [MaxMind](https://www.maxmind.com/) et de générer des fichiers d'ACL à l'aide de ce [script](http://phix.me/geodns/). Les performances sont en théorie moins bonnes (mais cette différence n'est pas vraiment constatée dans la pratique), mais l'installation est bien plus simple à maintenir.

### À l'aide de vues

* On récupère le script [GeoIP.py](http://phix.me/geodns/GeoIP.py) et on installe les dépendances nécessaires : 

~~~
# apt install python-mpmath
~~~

* exécuter le script. Il générera un fichier `GeoIP.acl`, avec une ACL par pays :

~~~
$ ./GeoIP.py MaxMind
~~~

* Configurer Bind pour servir une zone différente en fonction des pays :

~~~
include "/etc/bind/GeoIP.acl";

view "europe" {
  match-clients { FR; };
  recursion no;
  zone "example555.com" {
    type master;
    file "/etc/bind/db.example555-europe.db";
    allow-query { any; };
  };
};

view "north_america" {
  match-clients { US; CA; MX; };
  recursion no;
  zone "example555.com" {
    type master;
    file "/etc/bind/db.example555-north-america.db";
    allow-query { any; };
  };
};

view "other" {
  match-clients { any; };
  recursion no;
  zone "example555.com" {
    type master;
    file "/etc/bind/db.example555-other.db";
    allow-query { any; };
  };
};
~~~
Le script peut être mis en cron pour conserver des ACL à jour.


## FAQ

### journal out of sync

Si vous avez une erreur du type :

~~~
journal out of sync with zone
~~~

Lancer la commande :

~~~
# named -g
~~~

Voir <http://www.thedumbterminal.co.uk/?action=showArticle&articleId=168>

### ran out of space

Si vous avez une erreur du type (par exemple pour des enregistrements TXT ou SPF) :

~~~
ran out of space
~~~

Vous devez spliter vos champs, voir <http://www.jeoffrey54.com/article144/dns-sous-bind-ran-out-of-space>

### received control channel command 'stop'

En lançant votre démon, celui-ci est stoppé immédiatement avec un simple message d'arrêt : 

~~~
received control channel command 'stop'
~~~

…vérifiez si vous n'avez pas oublié l'option `-f` tout en utilisant systemd.

### Zone replica avec Bind 9.9

À partir de Bind 9.9, le stockage des zones répliquées se fait en binaire.
Avec une ancienne configuration vous aurez des erreurs :

~~~
zone example.com/IN: loading from master file /etc/bind/bak.example.com failed: not implemented
zone example.com/IN: unable to load from '/etc/bind/bak.example.com'; renaming file to '/etc/bind/db-5GoiCpdc' for failure analysis and retransferring.
~~~

Pour conserver l'ancien comportement il faut ajouter dans la configuration :

~~~
masterfile-format text;
~~~

Voir <http://geekdom.wesmo.com/2014/06/05/bind9-dns-slave-file-format/>

### Vérifier la configuration

* Globalement :

~~~
# named-checkconf /etc/bind/named.conf
~~~

* Pour une zone en particulier :

~~~
# named-checkzone example.com /etc/bind/db.example.com
~~~

* Pour un reverse :

~~~
# named-checkzone $reversed_IP.in-addr.arpa. /etc/bind/db.reverse.ripe$subnet
~~~


### Forcer un transfer sur le replica

Sur le replica :

~~~
# rndc reload example.com
zone refresh queued
~~~

### Pas d'IPv6 ou souci d'IPv6

Si malheureusement vous n'avez pas d'IPv6 sur votre serveur ou alors si la qualité de votre connexion IPv6 est insuffisante, vous devez forcer Bind à ne pas écouter en IPv6 ainsi :

~~~
listen-on-v6 { none; };
~~~

Et via le fichier `/etc/default/named` (`/etc/default/bind9` avant Bullseye) vous allez le forcer à n'effectuer des requêtes qu'en IPv4 :

~~~
OPTIONS="-u bind -4 -t /var/chroot-bind"
~~~

### master "caché"

L'enregistrement `SOA ns-hidden-master.example.com ...` doit bien pointer vers le master caché, dans ce cas `ns-hidden-master.example.com`
sinon les NOTIFY ne fonctionneront pas correctement.

### refresh: timeout retrying without EDNS master

Cela signifie que les requêtes EDNS ne fonctionnent pas vers le master, cela peut notamment être causé par un firewall qui tronque les paquets EDNS qui sont volumineux. On peut notamment contourner cela en désactivant EDNS vers le serveur concerné via `named.conf.options` :

~~~
options {
...
}
logging {
...
}
server 192.0.2.53 { edns no; };
~~~

### délai de transfert de zones

Voir <https://kb.isc.org/docs/aa-00726>

### Mise a jour du serial

Vous devez repérer un pattern unique sur la ligne du serial, par exemple `; serial`


On peut ainsi mettre à jour le serial d'une zone :

~~~
$ serial=$(date "+%Y%m%d%H")
$ sed -i "s/^\([ \t]*\)[0-9]\{10\}\([ \t]*; serial\)/\1$serial\2/" db.example.com
~~~

Si l'on doit automatiser, on utilisera les commandes suivantes pour prendre en compte plusieurs modifications le même jour :

~~~
zonefile=db.example.com
serial=$(grep -E '[ \t]*[0-9]{10}\s*; serial' $zonefile | sed "s/[ \t]*\([0-9]\{10\}\)[ \t]*; serial/\1/")
if [ `date "+%Y%m%d%H"` -gt $serial ]; then serial=$(date "+%Y%m%d%H"); else serial=$(( $serial + 1 )); fi
sed -i "s/^\([ \t]*\)[0-9]\{10\}\([ \t]*; serial\)/\1$serial\2/" $zonefile
~~~

### Configuration update-policy

Dans `update-policy`, les régles ACLs prennent la forme `( grant | deny ) identity ruletype  name   types`

> La première règle qui match un requête sera utilisé

* `identity` correspond à un fqdn ou le nom la clef TSIG
* `name` le nom de l'entrée dans la zone
* `types` le ou les types de l'entrée(s) dans la zone
* `ruletype` est le type de règle utilisé, il en existe une vingtene de valeurs, les plus importantes sont (WIP)

**Les `ruletype`s pricipaux** :

* `name` la règle match exactement avec le nom de l'entrée `name`
* `subdomain` match avec tous le sous-domaine et le domaine de l'entrée `name`
* `zonesub` match tous les sous-domaines de la zone contenant la configuration, il ne faut pas préciser d'entrée `name`
* `wildcard` match toute les [expansions wildcard](https://en.wikipedia.org/wiki/Wildcard_DNS_record) valides de l'entrée `name`
* `self` permet de faire correspondre `identity` directement avec le nom d'une entrée, il ne faut pas préciser d'entrée `name` ou sinon y mettre `.` ou la même valeur que `identity`. On peut aussi mettre directement `*` pour `identity` si le nom est bien le même que celui d'une entrée (cela permet de matcher automatiquement les clefs avec les noms d'entrées)
* `selfsub` fonctionne comme self *et* permet de mettre à jour aussi les sous-domaine du nom d'une entrée
* `selfwild` fonctionne comme self *mais* permet de mettre à jour *uniquement* les sous-domaine du nom d'une entrée
* Liste complète: <https://bind9.readthedocs.io/en/v9_18_2/reference.html#dynamic-update-policies>


### Problèmes de rafraîchissement des DNS secondaires 

Si on modifie les zones du primaire à la main, les secondaires doivent [désactiver les requêtes IXFR](#dns-primaires-et-secondaires), en faveur de l'AXFR.

L'IXFR fonctionne bien seulement avec les mises-à-jour DNS dynamiques (`nsupdate`).
