# DNS : interne et public
> **Pour qui :** le **mainteneur** du DNS, et l'**exploitant** d'un site qui publie les noms
> de ses locataires. Le nom du fichier date du temps où il ne couvrait que l'interne.
Le service DNS interne est la premiere capacite de plateforme.
> **Plancher de resolution independant du DNS.** Le socle (`hosts_statiques`, dans
> `serveur_debian`) genere `/etc/hosts` sur **chaque** VM depuis l'inventaire : tout
> l'ecosysteme se resout par nom **meme serveur DNS eteint** (et au bootstrap, avant
> que PowerDNS ne soit la). PowerDNS devient une **commodite** (zone, externe,
> dynamique), plus un point de defaillance.
## Vue d'ensemble
Deux services distincts, qui ne se parlent pas :
- **la résolution interne** — chaque écosystème résout ses propres noms (`*.internal`) et
l'Internet pour ses machines. Rien n'en sort.
- **le DNS public** — les zones publiques des locataires (`chezlepro.ca`, `technolibre.ca`),
écrites chez le locataire, servies à l'Internet par **deux sites**, sous deux noms.
```mermaid
flowchart LR
NET(("Internet"))
subgraph LOC["Les locataires — vivent au site de Chezlepro"]
direction TB
P1["infra-dns-01 · OPS-Chezlepro · 10.17.19.11
pdns@public :5300
primaire caché, signe DNSSEC"]
P2["infra-dns-01 · OPS-Technolibre · 10.23.19.11
pdns@public :5300
primaire caché, signe DNSSEC"]
end
subgraph SC["Site de Chezlepro"]
direction TB
BIF["bifrost-1
WAN 69.70.26.62:53"]
S1["site-dnspub-01 · 10.37.37.21
dnsdist :1053 → PowerDNS :53
dns1.chezlepro.ca"]
BIF -- "redirection :1053" --> S1
end
subgraph ST["Site de TechnoLibre"]
direction TB
REL["relais · VPS OVH
51.79.45.193"]
REQ["requin
opt9 RELAIS · opt10 lien"]
S2["site-dnspub-01 · 10.31.37.21
dnsdist :1053 → PowerDNS :53
dns1.technolibre.ca"]
REL -- "tunnel WireGuard" --> REQ
REQ -- "redirection :1053" --> S2
end
P1 & P2 -- "NOTIFY · AXFR signé TSIG" --> S1
P1 & P2 -. "NOTIFY · AXFR signé TSIG
par le lien entre les sites" .-> S2
NET -- "53" --> BIF
NET -- "53" --> REL
```
| Pièce | Rôle | Où | Section |
|---|---|---|---|
| plancher `/etc/hosts` | résout la flotte, DNS éteint | chaque VM | [Trois couches](#trois-couches-et-une-seule-est-optionnelle) |
| Unbound | LE résolveur d'un écosystème | `infra-dns-01` (locataire), `site-dns-01` (site) | idem |
| PowerDNS principal | autorité sur la zone `.internal` | boucle locale, derrière Unbound | idem |
| PowerDNS `pdns@public` | primaire caché des zones publiques | `infra-dns-01` du locataire, `:5300` | [Zones publiques](#zones-publiques) |
| PowerDNS + dnsdist | serveur de noms public du site, secondaire | `site-dnspub-01` de chaque site | [Deux sites](#deux-sites-deux-serveurs-de-noms) |
## Trois couches, et une seule est optionnelle
> **Ce paragraphe decrivait le modele d'avant le 2026-08-24** — un Unbound *sur chaque VM*,
> en opt-in. Ce n'est plus le cas : `client_resolveur` **n'installe plus rien**, et son
> integration est **universelle**, pas elective.
```text
1. hosts_statiques le PLANCHER : /etc/hosts genere sur chaque VM depuis l'inventaire
-> l'ecosysteme se resout DNS eteint. Jamais optionnel.
2. serveur_powerdns l'AUTORITATIF de la zone souveraine (un par ecosysteme)
3. serveur_resolveur le RECURSIF : UN seul Unbound pour tout le tenant, qui recurse
depuis la racine et delegue la zone souveraine a PowerDNS
client_resolveur l'integration : ecrit /etc/resolv.conf pour designer ce resolveur
```
**`client_resolveur` n'installe plus de demon** (2026-08-24). Il en posait un par VM — N
demons identiques de ~21 Mo pour quelques centaines de requetes. Il ne fait plus qu'une
chose : **ecrire `/etc/resolv.conf`**. Son integration est marquee `universelle: true`, et
elle n'a **aucune exemption, pas meme l'hote qui porte le resolveur** : il se sert
lui-meme. L'ancien nom (`client_unbound`) mentait des lors qu'il n'installait plus Unbound.
**L'integration suit l'existence du service, elle ne se declare pas.** Aucun
`serveur_resolveur` au plan rend `client_resolveur_actif` faux et le role ne touche a rien :
l'ecosysteme garde la resolution d'amorcage de cloud-init. C'est un choix valide, pas une
panne.
### Le chemin d'une requête interne
```mermaid
flowchart TB
Q["Une machine du locataire
cherche un nom"] --> H{"/etc/hosts
le plancher"}
H -- "machine de la flotte" --> A1["adresse — sans DNS"]
H -- "autre nom" --> U["Unbound · infra-dns-01:53
serveur_resolveur"]
U -- "*.internal et zone inverse
stub-zone" --> PA["PowerDNS principal
127.0.0.1:5300"]
U -- "tout le reste
récursion depuis la racine" --> NET(("Internet"))
Q -. "locataire neuf, sans résolveur à lui" .-> SD["site-dns-01
le résolveur du site
serveur_resolveur_site"]
```
- **Le résolveur est la seule porte.** Quand PowerDNS partage l'hôte d'Unbound, il écoute
sur la boucle locale (`5300`) : personne ne l'interroge directement (port `derive`,
`roles/serveur_powerdns/meta/flux.yml`).
- **Le site prête ses noms aux locataires neufs.** Tant qu'un locataire n'a pas son
résolveur, `client_resolveur` le fait interroger celui du site (`serveur_resolveur_site`,
dont la liste d'autorisation se dérive des locataires hébergés), puis le bascule chez lui.
- **Où mène un nom exposé, vu de l'intérieur** : `resolution_interne` dans
`plan/domaines.yml`. `service` (le site) : la zone mène au service lui-même — l'edge est
pour les personnes. `edge` (défaut, les locataires) : la machine passe par l'edge, parce
que certains services n'existent en HTTPS que derrière lui (Keycloak).
- **Une machine qui naît** interroge `dns_amorcage` (la patte de la frontière) tant que le
résolveur de son site n'existe pas.
## Groupes
```text
serveur_powerdns -> autoritatif interne (PowerDNS Authoritative)
serveur_resolveur -> LE recursif du tenant (Unbound), un seul
client_resolveur -> integration universelle : designe ce resolveur dans /etc/resolv.conf
```
## Zone initiale
La zone initiale est :
```text
exemple.internal
```
Elle est definie dans :
```text
instance/inventories//group_vars/serveur_powerdns.yml
```
## Enregistrement automatique
Le role `serveur_powerdns` genere la zone a partir de l'inventaire.
Les hotes qui remplissent ces conditions sont ajoutes automatiquement :
```text
hote dans hotes_actifs
ansible_host defini
```
Exemple :
```text
web-frontal-01 ansible_host=10.0.2.31
-> web-frontal-01.exemple.internal A 10.0.2.31
```
Les enregistrements additionnels explicites sont dans `serveur_powerdns_records`.
## Backend PowerDNS
Le premier jalon utilise le backend BIND de PowerDNS.
Raison :
- pas de dependance prematuree a PostgreSQL ;
- zone lisible et generee par Ansible ;
- idempotence simple ;
- bon point de depart pour la supervision.
Quand `serveur_postgresql` sera stable, il sera possible de migrer vers un backend SQL si le besoin operationnel le justifie.
## La bascule de `/etc/resolv.conf` est protegee
Le role `client_resolveur` ne bascule `/etc/resolv.conf` qu'apres avoir verifie que le
resolveur repond **deja** — pour l'interne *et* pour l'Internet :
```yaml
client_resolveur_apply: true
client_resolveur_confirm: true
```
Sans ces deux variables, le role prépare Unbound mais ne modifie pas `/etc/resolv.conf`.
Cette protection est volontaire : une mauvaise configuration DNS peut couper la resolution
de noms. C'est la dependance la plus dangereuse du lot — basculer un hote sur un resolveur
qui n'est pas encore pret le rend **muet**, et le runner qui devrait reparer tombe avec les
autres. Le playbook de groupe applique donc l'hote qui *porte* le resolveur avant ceux qui
s'y adressent, et la preuve **P44** refuse tout ecart entre cette declaration et lui.
## Le piege qui a coute deux jours : la racine signee nie notre TLD
`internal.` n'est **pas delegue dans la racine**, qui est signee : elle rend donc une preuve
NXDOMAIN *validee* pour ce TLD. Or `harden-below-nxdomain` — **actif par defaut** dans
Unbound — tient ce « non » pour prouve et repond NXDOMAIN pour **tout** nom sous
`internal.` depuis son cache, **sans jamais interroger la `stub-zone`** declaree plus bas.
La delegation etait correcte. L'autoritatif repondait juste. Pas une requete ne lui
parvenait.
Ce qui declenche l'empoisonnement : n'importe quelle question sur un nom inexistant sous
`internal.` — y compris la zone d'**un autre ecosysteme**, que ce resolveur ne sert pas et
va donc chercher a la racine. Sur un resolveur partage, ca arrive en permanence.
Et la panne parait **intermittente** : au redemarrage le cache est vide, tout fonctionne, on
conclut que c'est regle. Le remede mesure (2026-09-02) est `harden-below-nxdomain: no` dans
`roles/serveur_resolveur/templates/setops.conf.j2` — **pas** `aggressive-nsec`, qui traite
un autre symptome.
## Surveillance a prevoir
Les premiers checks utiles :
- service `pdns` actif ;
- port UDP/TCP 53 disponible ;
- SOA de `exemple.internal` resoluble ;
- enregistrement `ns1.exemple.internal` resoluble ;
- enregistrements des hotes actifs presents ;
- serial de zone attendu ;
- latence de resolution.
## Zones publiques
> Ajouté le 2026-09-16. Le mode `primaire-cache` était **validé par le schéma et consommé par
> rien** depuis sa création — *une autorité qu'on s'attribue sans l'exercer est une panne
> différée*.
`plan/domaines.yml` porte un champ `autorite` par domaine. Deux valeurs sont exercées :
| `autorite` | Ce que ça veut dire | Qui sert la zone |
|---|---|---|
| `auto-heberge` | zone **interne** (`.internal`), servie à l'écosystème seul | l'instance principale de `serveur_powerdns`, sur la boucle locale |
| `primaire-cache` | zone **publique** : le locataire l'écrit, le site la sert | `pdns@public` chez le locataire (primaire caché), `serveur_dns_public` au site (secondaire public) |
`delegue` est accepté par le validateur et **n'est consommé par aucun rôle**. Il ne faut pas
le déclarer en croyant qu'il fait quelque chose.
**Le locataire écrit, le site sert, un site pair réplique.** Le primaire reste chez le
locataire : quand il part, il emporte sa zone. Le détail — et ce que l'épreuve de PowerDNS a
appris — est dans `roles/serveur_dns_public/README.md`.
Trois règles, chacune payée ou mesurée :
- **TSIG seul ouvre le transfert.** `allow-axfr-ips` et TSIG sont *alternatifs* : une adresse
listée obtient la zone sans signature. L'instance publique n'autorise que la boucle locale.
- **Une instance à part pour le public.** Faire écouter l'instance principale sur l'adresse
de l'hôte aurait permis au serveur public du site d'interroger la zone `.internal`.
- **Le serial suit le contenu.** Un serial figé ferait garder au secondaire l'ancienne zone
pour toujours.
**Rien n'est exposé à Internet en phase 1.** P82 refuse d'exposer le serveur public tant
que toutes ses zones ne sont pas signées DNSSEC.
### Ce qu'une zone publique porte
> Ajouté le 2026-09-16, phase 2. Une zone qui ne portait que ses expositions web aurait coupé
> le courriel de production à la minute où le registraire l'aurait suivie.
Chaque zone `primaire-cache` déclare ses **enregistrements** au plan (`enregistrements:` dans
`plan/domaines.yml`) : A, AAAA, CNAME, MX, TXT, CAA. `valider_domaines` refuse une adresse
privée, une cible incomplète, un CNAME à l'apex ou à côté d'un autre type, un MX sans
priorité, un TXT avec guillemets ou hors ASCII, un nom écrit en absolu (`mx.chezlepro.ca`
sous `chezlepro.ca`), et tout enregistrement dans une zone que Set-OPS n'écrit pas.
Le **SOA et les NS** ne se déclarent pas : ils désignent le serveur de noms du site, sous le
nom que **le site** déclare (`dns_public_nom`, contrat du site, reçu par chaque locataire).
Seule la zone qui contient ce nom porte son A. Le premier choix, `ns1.`, aurait déplacé
en silence un `ns1.chezlepro.ca` qui existe en production vers une autre adresse.
**Avant de basculer : `make dns-bascule-devis`.** Il compare le plan au DNS en service, nom
par nom et type par type, sur les noms déclarés et une liste de sondes usuelles. Il rend ce
qui serait **perdu**, **changé**, **ajouté**, ou **abandonné en connaissance de cause** (les
marques `heritage=external-dns`), et les préalables : le serveur de noms doit déjà résoudre
publiquement, et il en faut **deux** (exigence du registre `.ca`). Une mesure qui échoue rend
« mesure impossible », jamais « absent ». Sa limite est écrite à chaque rapport : sans
transfert de zone, un export chez le fournisseur actuel reste la seule preuve d'exhaustivité.
Le devis sonde aussi **la colle de chaque serveur désigné** — ceux du site et ceux des pairs
([Deux sites](#deux-sites-deux-serveurs-de-noms)).
Au 2026-10-14 : les deux zones sont servies par les deux sites, signées, au même serial ;
**aucune n'est basculée chez son registraire**. `chezlepro.ca` reproduit la production
(rien ne serait perdu). La décision pour `technolibre.ca` revient au responsable désigné de
TechnoLibre.
### Signature DNSSEC : le locataire signe, avec la clé de sa voûte
> Ajouté le 2026-09-16, phase 2. Éprouvé sur les machines réelles avant d'être codé.
`dnssec: true` sur une zone `primaire-cache` la fait signer **par le primaire du locataire**,
avec **une** clé CSK ECDSA P-256 tenue dans **sa** voûte (`vault_dnssec_`). Le site ne
détient aucune clé : il reçoit la zone déjà signée et la sert telle quelle (PowerDNS pose
`PRESIGNED` au transfert).
| Geste | Commande | Écrit ? |
|---|---|---|
| Créer la clé d'une zone | `python3 scripts/dnssec.py generer ` | la voûte du locataire (copie de sûreté chiffrée, relue avant et après) |
| Lire le DS à remettre au registraire | `make dnssec-ds` | rien — calculé depuis la voûte, sans la machine |
| Voûte, plan et registre concordent-ils ? | `make dnssec-verifier` | rien |
Pourquoi la clé ne naît pas sur la machine : une reconstruction depuis zéro signerait avec
une **autre** clé, le DS du registraire ne correspondrait plus, et le domaine deviendrait
**BOGUS** pour tout résolveur validant, sans qu'aucune machine soit en panne.
Trois règles, chacune mesurée :
- **Changer la clé ou retirer la signature pendant qu'un DS est publié est refusé** par l'outil
de la machine (`setops-dnssec-zone`) comme par `dnssec.py`. Une mesure du DS qui échoue vaut
refus.
- **Le serial servi est l'heure (`SOA-EDIT EPOCH`).** PowerDNS renouvelle ses signatures chaque
semaine (inception au début de la semaine précédente, expiration deux semaines après le
début de la semaine courante). `INCEPTION-EPOCH`, essayé d'abord, ne dépassait notre serial
qu'au moment où les signatures du site expiraient. Avec l'heure, le site retire la zone à
chaque rafraîchissement ; les signatures servies gardent 7 à 14 jours.
- **L'état DNSSEC fait partie du corps de la zone.** Signer ne change aucun enregistrement :
sans la ligne `; DNSSEC : …`, le serial n'aurait pas bougé et le site aurait gardé la version
non signée.
La sonde `zones-publiques` du site alerte sur une zone signée servie **nue**, avertit sous
6 jours de signature restante, et alerte sous 3.
**Ordre de la mise en service** : basculer les serveurs de noms (zone signée, sans DS : le
domaine reste « non sécurisé », pas cassé), vérifier, **puis** remettre le DS au registraire.
Jamais l'inverse.
## Deux sites, deux serveurs de noms
> Ajouté le 2026-10-14. Le registre des `.ca` exige deux serveurs de noms ; deux serveurs
> sur le même réseau tomberaient ensemble. Le second est au site de TechnoLibre, tenu par une
> autre organisation, sur un autre réseau.
**Chaque site sert les zones publiques de tous les locataires qu'il connaît : les siens, et
ceux de son pair.** Les deux serveurs sont **secondaires** l'un comme l'autre, et tirent
chacun la zone **directement du primaire du locataire** — jamais l'un de l'autre
(`disable-axfr=yes` au site). Un site qui tombe n'emporte donc pas la copie de l'autre.
| Site | Serveur | Nom public | Adresse publique | Chemin depuis l'Internet |
|---|---|---|---|---|
| Chezlepro | `site-dnspub-01` · `10.37.37.21` | `dns1.chezlepro.ca` | `69.70.26.62` | WAN de `bifrost-1` → `:1053` |
| TechnoLibre | `site-dnspub-01` · `10.31.37.21` | `dns1.technolibre.ca` | `51.79.45.193` | relais (VPS) → tunnel `opt9` de `requin` → `:1053` |
### Publier une zone
```mermaid
sequenceDiagram
autonumber
participant X as Exploitant du locataire
participant P as pdns@public
infra-dns-01 du locataire
participant C as site-dnspub-01
site de Chezlepro
participant T as site-dnspub-01
site de TechnoLibre
X->>P: rôle serveur_powerdns — la zone est rendue depuis le plan
Note over P: SOA et NS = dns1 du site + dns1 de chaque pair
serial = suit le contenu · signée avec la clé de la voûte
P-)C: NOTIFY (only-notify : les seuls serveurs du contrat)
P-)T: NOTIFY, par le lien entre les sites
C->>P: SOA ? (UDP 5300)
C->>P: AXFR signé TSIG (TCP 5300)
P-->>C: zone déjà signée — servie telle quelle (PRESIGNED)
T->>P: SOA ? puis AXFR signé TSIG, par le lien
P-->>T: zone déjà signée
```
- **Le locataire écrit, les sites servent.** Les deux sites ne détiennent ni la zone en
source, ni la clé DNSSEC.
- **`only-notify` et `also-notify`** bornent les notifications aux adresses du contrat du
site (`dns_public_site` et `dns_public_pairs`). Sans eux, le primaire notifierait les
adresses publiques de ses NS, depuis l'intérieur du locataire.
- **Le transfert passe par le lien** quand le secondaire est sur l'autre site : la frontière
du site où vit le locataire laisse entrer le transfert et sortir les NOTIFY ; celle du site
qui réplique pose une route `/32` vers chaque primaire. Le devis les dérive de la fiche que
porte chaque locataire (où il vit), et P24 juge ces routes à part.
### Une requête de l'Internet
```mermaid
sequenceDiagram
participant R as Résolveur sur Internet
participant F as Frontière
bifrost-1 ou requin
participant D as dnsdist :1053
participant A as PowerDNS :53
R->>F: chezlepro.ca MX ? (port 53)
Note over F: TechnoLibre : arrive par le relais et le tunnel opt9,
et repart par lui (reply-to RELAIS_GW)
F->>D: redirection vers :1053
Note over D: refuse NOTIFY, UPDATE, AXFR · tronque ANY en UDP
débit borné par /24
D->>A: requête admise
A-->>D: réponse signée
D-->>R: réponse
```
**Sans `reply-to`, la réponse du site de TechnoLibre se perdrait** : elle repartirait par la
route par défaut, le WAN, qui n'a qu'une adresse privée en amont (mesuré le 2026-10-13).
### Les flux
| Flux | De | Vers | Port | Chemin |
|---|---|---|---|---|
| Requête publique | Internet | dnsdist du site | 53 → 1053, UDP et TCP | redirection de la frontière ; par le relais au site de TechnoLibre |
| Requête admise | dnsdist | PowerDNS du site | 53, boucle de l'hôte | local |
| NOTIFY | `pdns@public` du locataire | PowerDNS de chaque site | 53/UDP | local ; par le lien vers l'autre site |
| SOA, puis AXFR TSIG | PowerDNS de chaque site | `pdns@public` du locataire | 5300, UDP puis TCP | local ; par le lien depuis l'autre site |
| Résolution interne | machine du locataire | Unbound de son écosystème | 53 | dans l'écosystème |
| Zone `.internal` | Unbound | PowerDNS principal | 5300, boucle locale | local |
| Récursion | Unbound | Internet | 53 | sortie de la frontière |
Ces flux sont déclarés par les rôles (`meta/flux.yml` de `serveur_dns_public`,
`serveur_powerdns`, `serveur_resolveur`) et par le lien (`opnsense_liens_sites`) ; le devis
de la frontière et le pare-feu Proxmox en dérivent leurs règles. Aucun n'est écrit à la
main sur une frontière.
### Ce qui se déclare, et où
| Clé | Fichier | Ce qu'elle décide |
|---|---|---|
| `autorite: primaire-cache`, `dnssec`, `enregistrements` | `plan/domaines.yml` du **locataire** | la zone est publique, signée, et ce qu'elle porte |
| `dns_public_nom` | `plan/10-intrants.yml` du **site** | le nom de SON serveur de noms (`dns1.`) |
| `dns_public_pairs` | plan du **site**, des deux côtés | le site pair qui réplique ; `inactif` n'émet rien |
| `opnsense_liens_sites` | `opnsense.yml` du **site** | le tunnel que prennent NOTIFY et transferts |
| `opnsense_wan_ip` | `opnsense.yml` du **site** | l'adresse publique de son serveur de noms |
Le **contrat du site** (`make site-intrants`) en tire, pour chaque locataire, ce qu'il
dépose dans sa fiche (`fiche-site.yml`) : `dns_public_site`, `dns_public_pairs`,
`dns_public_nom` et `dns_public_noms_pairs` (le nom et l'adresse publique du serveur de
chaque pair, lus dans **son** plan et **sa** carte, par les mêmes fonctions que pour le site
lui-même).
### Les NS d'une zone
```mermaid
flowchart LR
PC["plan du site de Chezlepro
dns_public_nom: dns1.chezlepro.ca"] --> K["contrat du site
fiche du locataire"]
PT["plan du site de TechnoLibre
dns_public_nom: dns1.technolibre.ca
+ opnsense_wan_ip"] -- "dns_public_noms_pairs" --> K
K --> Z["zone-publique.db.j2
chez le locataire"]
Z --> NS["chezlepro.ca — NS dns1.chezlepro.ca, NS dns1.technolibre.ca
A de dns1.chezlepro.ca : seulement dans chezlepro.ca
A de dns1.technolibre.ca : seulement dans technolibre.ca"]
```
- **Un site, un nom** : `dns1` sous le domaine de l'organisation qui tient le site. Pas
`ns1`/`ns2`, qui existent en production à d'autres adresses.
- **Seule la zone qui contient un nom en porte le A**, à l'adresse publique de **son** site.
Les autres zones ne le désignent que par son nom.
- **Sans pair, le rendu est identique octet pour octet** à celui d'un site seul.
### Les gardes
| Garde | Où | Ce qu'elle refuse |
|---|---|---|
| relation des deux côtés | P82 (`prouver._dns_paires`) | un pair déclaré d'un seul côté — le second serveur serait vide sans que personne le voie |
| un pair nomme son serveur | P82 (`prouver._dns_paires`) | un pair actif sans `dns_public_nom` — ses zones n'en désigneraient qu'un, alors qu'il les sert |
| signé avant exposé | P82 | exposer un serveur public dont une zone n'est pas signée |
| nom et adresse du pair | `serveur_powerdns` (`zones-publiques.yml`) | un nom mal formé, une adresse privée, un nom égal au nôtre, un nom redéclaré |
| contrat déposé | `make site-intrants-verifier` | une fiche de locataire qui diffère du contrat (une adresse faussée la rend NON CONFORME) |
| routes du lien | P24 | une route `/32` qu'aucune règle du lien n'utilise |
| zone servie nue | sonde `zones-publiques` | une zone signée servie sans signature ; signatures sous 6 jours (avertit), sous 3 (alerte) |
### Basculer chez un registraire
L'ordre, pour chaque domaine :
1. **`make dns-bascule-devis`** — rien ne serait perdu, et chaque serveur désigné résout déjà
publiquement.
2. **La colle** chez le registraire du domaine qui contient le nom : `dns1.chezlepro.ca`
chez celui de `chezlepro.ca`, `dns1.technolibre.ca` chez celui de `technolibre.ca`.
3. **Les deux NS** chez le registraire de la zone basculée. Zone signée, sans DS : le domaine
reste « non sécurisé », pas cassé.
4. **Vérifier** depuis l'Internet, sur les deux adresses.
5. **Puis seulement le DS** (`make dnssec-ds`). Jamais l'inverse.
**État au 2026-10-14** : les deux sites servent `chezlepro.ca` et `technolibre.ca` au même
serial, chacune avec deux NS signés. **Ni la colle ni les NS ne sont encore chez les
registraires.**