Set-OPS-Public/roles/serveur_powerdns/templates/zone-publique.db.j2
Daniel Allaire d152e32328 dns public : chaque zone designe aussi le serveur de noms du site pair
Le contrat du site porte dns_public_noms_pairs : nom et adresse publique du
serveur de chaque pair actif, lus dans son plan et sa carte de frontiere par
les memes fonctions que pour le site lui-meme. Le gabarit de zone ecrit un NS
par serveur ; la zone qui contient un de ces noms en porte le A, a l'adresse
de son site. Sans pair, le rendu est identique a l'ancien.

Garde au role (nom, adresse publique, jamais redeclare) ; la preuve du DNS
public refuse un pair actif sans dns_public_nom ; dns_bascule compte les
serveurs designes et sonde la colle de chacun.

Deploye chez les deux locataires : les deux serveurs publics servent
chezlepro.ca et technolibre.ca au meme serial, deux NS signes.

CHANGELOG 151.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:44:24 -04:00

59 lines
3.6 KiB
Django/Jinja

{# LE SERVEUR DE NOMS EST CELUI DU SITE, SOUS LE NOM QUE LE SITE DECLARE (2026-09-16).
La premiere version ecrivait `ns1.<zone>` dans chaque zone. Or `ns1.chezlepro.ca`
existe en production, vers une autre adresse : basculer l'aurait deplace en silence.
Toutes les zones du site designent desormais un seul nom, `dns_public_nom`, et seule la
zone qui le contient porte son A. #}
{% set _ns = serveur_powerdns_dns_public_nom | trim | regex_replace('\\.$', '') %}
$ORIGIN {{ zone_publique }}.
$TTL {{ serveur_powerdns_ttl }}
@ IN SOA {{ _ns }}. hostmaster.{{ zone_publique }}. (
{{ serveur_powerdns_serial_public }}
{{ serveur_powerdns_soa_refresh }}
{{ serveur_powerdns_soa_retry }}
{{ serveur_powerdns_soa_expire }}
{{ serveur_powerdns_soa_minimum }}
)
{# L'ETAT DNSSEC FAIT PARTIE DU CORPS. Signer, changer de cle ou retirer la signature ne
change aucun enregistrement : sans cette ligne, le serial ne bougerait pas, et le site
garderait la version non signee (eprouve le 2026-09-16). L'empreinte d'une cle aleatoire
de 256 bits ne revele rien d'elle. #}
{% set _cle = lookup('vars', 'vault_dnssec_' ~ (zone_publique | lower | regex_replace('[^a-z0-9]', '_')), default='') %}
; DNSSEC : {{ ('signee, cle ' ~ (_cle | hash('sha256'))[:12]) if ((domaines_publics[zone_publique].dnssec | default(false) | bool) and _cle) else 'non signee' }}
{# ET LE SERVEUR DE CHAQUE SITE PAIR (2026-10-14). Il replique la zone par le tunnel entre les
sites ; sans son NS, il la servirait sans que personne le designe. Meme regle pour son A :
seule la zone qui contient son nom le porte, a l'adresse publique de SON site. #}
{% set _ns_tous = {_ns: serveur_powerdns_ip_publique} %}
{% for _n, _ip in (serveur_powerdns_dns_public_noms_pairs | default({})).items() %}
{% set _ = _ns_tous.update({(_n | trim | regex_replace('\\.$', '')): _ip}) %}
{% endfor %}
{% for _n in _ns_tous %}
@ IN NS {{ _n }}.
{% endfor %}
{% for _n, _ip in _ns_tous.items() if _n.endswith('.' ~ zone_publique) %}
{{ _n[:-(zone_publique | length + 1)] }} IN A {{ _ip }}
{% endfor %}
{# UNE ZONE PUBLIQUE NE PORTE QUE DES ADRESSES PUBLIQUES.
Un nom expose sous ce domaine resout vers l'adresse publique DU LOCATAIRE (2026-09-29) —
la frontiere la redirige vers son web frontal. Jamais vers une adresse interne : elle ne
resoudrait pour personne dehors, et renseignerait sur le reseau dedans. Les serveurs de
noms, eux, restent a l'adresse du site (ci-dessus). #}
{% for expo in serveur_powerdns_expositions | default([]) if expo.domaine == zone_publique %}
{{ expo.fqdn[:-(zone_publique | length + 1)] if expo.fqdn != zone_publique else '@' }} IN A {{ serveur_powerdns_ip_expositions }}
{% endfor %}
{# LES ENREGISTREMENTS DECLARES AU PLAN : courriel, verifications, noms historiques.
`valider_domaines` les a deja passes au crible (adresses publiques, cibles completes,
CNAME seul a son nom, texte sans guillemet). Le gabarit ne fait que les ecrire. Un TXT
plus long que 255 octets — une cle DKIM — est coupe en tranches : un resolveur les
recolle, et une seule chaine trop longue rendrait la zone illisible. #}
{% for enr in (domaines_publics | default({}))[zone_publique].enregistrements | default([]) %}
{% set _t = enr.type | upper %}
{% set _v = enr.valeur | string | trim %}
{{ enr.nom }} {{ enr.ttl | default('') }} IN {{ _t }} {% if _t in ['A', 'AAAA'] %}{{ _v }}{% elif _t == 'CNAME' %}{{ _v.rstrip('.') }}.{% elif _t == 'MX' %}{{ enr.priorite }} {{ _v.rstrip('.') }}.{% elif _t == 'CAA' %}0 {{ enr.etiquette }} "{{ _v }}"{% elif _t == 'TXT' %}{% for i in range(0, _v | length, 255) %}"{{ _v[i:i + 255] }}"{{ ' ' if not loop.last }}{% endfor %}{% endif %}
{% endfor %}