--- # --- LES REFUS, AVANT DE TOUCHER A QUOI QUE CE SOIT ----------------------------------- # UNE ZONE INTERNE N'ARRIVE JAMAIS ICI. Ce serveur repond a Internet : une zone `.internal` # servie ici serait la carte des machines d'un locataire offerte au monde. La derivation ne # retient que `primaire-cache` ; ce refus garde le cas ou elle se tromperait. - name: Refuser une zone interne sur le serveur public ansible.builtin.assert: that: - >- (serveur_dns_public_relations | map(attribute='zones') | flatten | select('search', '\.internal\.?$') | list | length) == 0 fail_msg: >- Une zone `.internal` est destinee au serveur DNS PUBLIC. Elle publierait la carte des machines d'un locataire. Seules les zones `autorite: primaire-cache` y ont leur place. quiet: true # UNE RELATION SANS SECRET N'EST PAS UNE RELATION NON SIGNEE : C'EST UN REFUS. Un # secondaire qui tirerait sans cle ne recevrait rien (le primaire n'autorise aucune # adresse) — ou pire, recevrait tout le jour ou quelqu'un ajouterait son adresse a # `allow-axfr-ips`, et TSIG deviendrait decoratif sans le moindre message. - name: Exiger un secret TSIG pour chaque relation ansible.builtin.assert: that: - lookup('vars', item.secret_var, default='') | length > 0 fail_msg: >- {{ item.ecosysteme }} : `{{ item.secret_var }}` est absent de la voute du site. Sans lui, la zone de ce locataire ne se transfere pas — et aucun repli non signe n'est prevu, par construction. quiet: true loop: "{{ serveur_dns_public_relations }}" loop_control: label: "{{ item.ecosysteme }}" # --- L'INSTALLATION ------------------------------------------------------------------- - name: Installer PowerDNS Authoritative ansible.builtin.apt: name: "{{ serveur_dns_public_paquets }}" state: present update_cache: true cache_valid_time: 3600 # Le secondaire ECRIT les zones qu'il recoit : le repertoire appartient a `pdns`. - name: Créer le répertoire des zones secondaires ansible.builtin.file: path: "{{ serveur_dns_public_zones_dir }}" state: directory owner: pdns group: pdns mode: "0750" - name: Déployer la configuration du serveur public ansible.builtin.template: src: setops-dns-public.conf.j2 dest: /etc/powerdns/pdns.d/setops-dns-public.conf owner: root group: pdns mode: "0640" notify: Redemarrer le DNS public - name: Déployer l'index des zones secondaires ansible.builtin.template: src: bindbackend.conf.j2 dest: "{{ serveur_dns_public_bind_config }}" owner: root group: pdns mode: "0640" notify: Redemarrer le DNS public # LA BASE APPARTIENT A PDNS, ET SON REPERTOIRE AUSSI (mesure le 2026-09-16, en production). # # La premiere version posait la base dans `/var/lib/powerdns`, qui appartient a root. Le # fichier etait bien a pdns — mais SQLite ecrit son JOURNAL a cote du fichier, dans le # repertoire. `pdns@public` refusait donc de demarrer : « attempt to write a readonly # database ». L'epreuve de l'outil ne pouvait pas le voir : elle tournait dans un /tmp qui # appartenait a l'utilisateur qui la lancait. # # Toutes les commandes `pdnsutil` s'executent donc en `pdns`. Lancees en root, elles # laisseraient un journal appartenant a root au premier incident — et la meme panne # reviendrait, plus tard, sans cause visible. - name: Créer le répertoire de la base, à pdns ansible.builtin.file: path: "{{ serveur_dns_public_dnssec_db | dirname }}" state: directory owner: pdns group: pdns mode: "0750" - name: Créer la base des clés TSIG become: true become_user: pdns # voir « la base appartient a pdns » ansible.builtin.command: cmd: "pdnsutil create-bind-db {{ serveur_dns_public_dnssec_db }}" creates: "{{ serveur_dns_public_dnssec_db }}" when: not ansible_check_mode - name: Confier la base des clés à pdns ansible.builtin.file: path: "{{ serveur_dns_public_dnssec_db }}" owner: pdns group: pdns mode: "0640" when: not ansible_check_mode # --- LES CLES TSIG, UNE PAR RELATION -------------------------------------------------- # # On COMPARE avant d'ecrire, et c'est ce qui rend la rotation possible : un secret change # dans la voute doit remplacer l'ancien. Importer seulement quand le nom manque laisserait # la vieille cle en place pour toujours, et le transfert echouerait sans rien dire. - name: Relever les clés TSIG en place become: true become_user: pdns # voir « la base appartient a pdns » ansible.builtin.command: cmd: pdnsutil list-tsig-keys register: serveur_dns_public_cles changed_when: false no_log: true when: not ansible_check_mode - name: Poser ou remplacer la clé TSIG de chaque relation become: true become_user: pdns # voir « la base appartient a pdns » ansible.builtin.command: argv: - pdnsutil - import-tsig-key - "{{ item.cle_tsig }}" - hmac-sha256 - "{{ lookup('vars', item.secret_var) }}" loop: "{{ serveur_dns_public_relations }}" loop_control: label: "{{ item.ecosysteme }} → {{ item.cle_tsig }}" when: - not ansible_check_mode - (item.cle_tsig ~ '. hmac-sha256. ' ~ lookup('vars', item.secret_var)) not in (serveur_dns_public_cles.stdout | default('')) changed_when: true # n'arrive ici que si la cle manque ou differe no_log: true notify: Redemarrer le DNS public - name: Relever les métadonnées de transfert become: true become_user: pdns # voir « la base appartient a pdns » ansible.builtin.command: argv: [pdnsutil, get-meta, "{{ item.1 }}", AXFR-MASTER-TSIG] loop: "{{ serveur_dns_public_relations | subelements('zones') }}" loop_control: label: "{{ item.1 }}" register: serveur_dns_public_meta changed_when: false failed_when: false when: not ansible_check_mode - name: Exiger la signature TSIG sur chaque transfert entrant become: true become_user: pdns # voir « la base appartient a pdns » ansible.builtin.command: argv: [pdnsutil, set-meta, "{{ item.item.1 }}", AXFR-MASTER-TSIG, "{{ item.item.0.cle_tsig }}"] loop: "{{ serveur_dns_public_meta.results | default([]) }}" loop_control: label: "{{ item.item.1 }} ← {{ item.item.0.cle_tsig }}" when: - not ansible_check_mode - ("= " ~ item.item.0.cle_tsig) not in (item.stdout | default('')) changed_when: true # n'arrive ici que si la metadonnee manque ou differe notify: Redemarrer le DNS public - name: Activer et démarrer PowerDNS ansible.builtin.systemd: name: pdns enabled: true state: started when: not ansible_check_mode # --- LE FRONTAL PUBLIC (dnsdist) ----------------------------------------------------- # # Ce que l'Internet frappe. PowerDNS garde le 53 de la machine pour les notifications des # primaires ; la frontiere redirige son 53 public vers ce frontal, jamais vers PowerDNS. - name: Déployer la configuration du frontal public ansible.builtin.template: src: dnsdist.conf.j2 dest: /etc/dnsdist/dnsdist.conf owner: root group: _dnsdist mode: "0640" validate: dnsdist --check-config -C %s notify: Redemarrer le frontal public - name: Activer et démarrer le frontal public ansible.builtin.systemd: name: dnsdist enabled: true state: started when: not ansible_check_mode # --- SONDE (docs/supervision-conception.md) ------------------------------------------- - name: Assurer le répertoire des sondes de supervision ansible.builtin.file: path: /usr/local/lib/setops/sondes state: directory owner: root group: root mode: "0755" - name: Déposer la sonde « zones-publiques » ansible.builtin.template: src: sonde-zones-publiques.sh.j2 dest: /usr/local/lib/setops/sondes/zones-publiques.sh owner: root group: root mode: "0750"