D-71 : PKI et DNS debout avant tout, et la zone inverse manquait
Contrainte de l'exploitant apres avoir vu backup-01 et collab-01 crees avant l'AC et le DNS. Ma premiere reponse etait incomplete : un clone est bien inerte, mais un echec a la 12e VM coute 40 minutes sans rien deployer, et le journal donne l'impression que le moteur ignore ses couches. Mesure avant de coder : - enregistrements A : DEJA derives du plan (zone generee depuis hotes_actifs) - zone inverse / PTR : n'existe NULLE PART, aucun role ne touche in-addr.arpa - ordre d'amorcage : aucun, deployer-tout est par couches Le premier point a reduit le travail de moitie — j'allais ecrire un enrolement DNS par hote alors que la zone directe etait deja correcte. Zone inverse derivee du supernet (27.10.in-addr.arpa), PTR issus de la MEME source que les A : pas d'endroit ou elles puissent diverger. Vide si le supernet n'est pas un /16. _amorcer-socle monte l'AC puis le DNS completement avant deployer-tout ; les deux derives de applications.<app>.hote, dans un ordre causal et non alphabetique. Deux exceptions assumees : l'AC s'auto-signe, le DNS pose son propre enregistrement. P10 a attrape un handler que je venais d'inventer (Recharger PowerDNS au lieu de Validate and reload PowerDNS) avant tout deploiement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
872d590031
commit
1295eeaf4e
11 changed files with 227 additions and 2 deletions
42
CHANGELOG.md
42
CHANGELOG.md
|
|
@ -1,5 +1,47 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-08 — D-71 : PKI et DNS debout avant tout le reste, et la zone inverse
|
||||||
|
|
||||||
|
Contrainte posée par l'exploitant en voyant `backup-01` et `collab-01` créés avant l'AC et
|
||||||
|
le DNS : **une PKI et un DNS fonctionnels avant toute chose** ; puis par VM, socle →
|
||||||
|
enrôlement PKI → enregistrement DNS (A et PTR).
|
||||||
|
|
||||||
|
Ma première réponse était incomplète. J'avais expliqué qu'un clone est inerte et que
|
||||||
|
`site.yml` respecte bien les couches — c'est exact, mais ça esquivait deux points justes :
|
||||||
|
un échec de création à la douzième VM coûte quarante minutes sans rien déployer, et un
|
||||||
|
journal qui montre `backup-01` en tête donne l'impression que le moteur ignore ses propres
|
||||||
|
couches.
|
||||||
|
|
||||||
|
**Ce qui manquait vraiment, mesuré avant de coder :**
|
||||||
|
|
||||||
|
```
|
||||||
|
enregistrements A → DEJA derives du plan (zone generee depuis `hotes_actifs`)
|
||||||
|
zone inverse / PTR → n'existe NULLE PART — aucun role ne touche `in-addr.arpa`
|
||||||
|
ordre d'amorcage → aucun : `deployer-tout` est par couches, pas par hote
|
||||||
|
```
|
||||||
|
|
||||||
|
Le premier point a réduit le travail de moitié : je m'apprêtais à écrire un enrôlement DNS
|
||||||
|
par hôte alors que la zone directe se dérivait déjà correctement.
|
||||||
|
|
||||||
|
**La zone inverse** est dérivée du supernet, comme le reste : un `/16` `10.(10+index).0.0`
|
||||||
|
donne `(10+index).10.in-addr.arpa` — `27.10.in-addr.arpa` ici. Les PTR viennent de la
|
||||||
|
**même source** que les A (`hotes_actifs` et son `ansible_host`) : deux zones alimentées
|
||||||
|
par une seule vérité, donc pas d'endroit où elles puissent diverger. Vide si le supernet
|
||||||
|
n'est pas un `/16` — mieux vaut pas de zone inverse qu'une zone fausse.
|
||||||
|
|
||||||
|
**`_amorcer-socle`** monte l'AC puis le DNS **complètement**, hôte par hôte, avant
|
||||||
|
`deployer-tout`. Les deux se dérivent de `applications.<app>.hote` ; l'ordre entre eux
|
||||||
|
n'est pas alphabétique mais causal — le DNS a besoin d'un certificat, l'autorité n'a besoin
|
||||||
|
de personne.
|
||||||
|
|
||||||
|
**Deux exceptions structurelles, nommées plutôt que découvertes à l'exécution** : l'AC
|
||||||
|
s'auto-signe, et le DNS pose son propre enregistrement. La règle « socle → PKI → DNS » ne
|
||||||
|
peut pas s'appliquer à ceux qui la rendent possible.
|
||||||
|
|
||||||
|
Au passage, le harnais a attrapé une faute que je venais d'introduire : j'avais inventé un
|
||||||
|
handler `Recharger PowerDNS` qui n'existe pas — le rôle écoute `Validate and reload
|
||||||
|
PowerDNS`. P10 l'a nommée avant tout déploiement.
|
||||||
|
|
||||||
## 2026-08-08 — Une question de l'exploitant trouve un trou dans P31, une heure après
|
## 2026-08-08 — Une question de l'exploitant trouve un trou dans P31, une heure après
|
||||||
|
|
||||||
« Pourquoi pas `make myDay` ? » — la cible existe, c'est un alias strict de
|
« Pourquoi pas `make myDay` ? » — la cible existe, c'est un alias strict de
|
||||||
|
|
|
||||||
18
Makefile
18
Makefile
|
|
@ -594,11 +594,29 @@ reconstruire: _instance-requise ## Reconstruit un ecosysteme depuis zero : VM pu
|
||||||
fi; \
|
fi; \
|
||||||
$(MAKE) flotte-creer CONFIRMER=true; \
|
$(MAKE) flotte-creer CONFIRMER=true; \
|
||||||
$(MAKE) _attendre-flotte; \
|
$(MAKE) _attendre-flotte; \
|
||||||
|
$(MAKE) _amorcer-socle; \
|
||||||
$(MAKE) deployer-tout CONFIRMER=true
|
$(MAKE) deployer-tout CONFIRMER=true
|
||||||
|
|
||||||
# « Go ahead, make my day. » — LE bouton rouge : alias de reconstruire (Clint Eastwood).
|
# « Go ahead, make my day. » — LE bouton rouge : alias de reconstruire (Clint Eastwood).
|
||||||
# Cree toutes les VM puis deploie tout, en une commande. Garde CONFIRMER=true.
|
# Cree toutes les VM puis deploie tout, en une commande. Garde CONFIRMER=true.
|
||||||
.PHONY: myDay
|
.PHONY: myDay
|
||||||
|
# « Une PKI et un DNS fonctionnels avant toute chose » — contrainte d'architecture du
|
||||||
|
# 2026-08-08. `deployer-tout` deroule par COUCHES : tous les hotes recoivent le socle,
|
||||||
|
# puis tous recoivent la couche suivante. C'est correct, mais ca laisse chaque VM
|
||||||
|
# reclamer un certificat a une autorite qui n'est pas encore debout — et l'echec se lit
|
||||||
|
# alors comme un defaut du role, pas comme un defaut d'ordre.
|
||||||
|
#
|
||||||
|
# On monte donc l'AC puis le DNS COMPLETEMENT d'abord, hote par hote (`make deployer`
|
||||||
|
# ordonne deja les groupes d'un hote par couches). Les deux se DERIVENT du plan :
|
||||||
|
# deplacer l'autorite deplace l'amorcage avec elle.
|
||||||
|
_amorcer-socle: ansible-runtime _instance-requise
|
||||||
|
@set -e; \
|
||||||
|
for h in $$(python3 scripts/socle_amorcage.py); do \
|
||||||
|
printf '\n=== Amorcage du socle : %s ===\n' "$$h"; \
|
||||||
|
$(MAKE) deployer HOTE="$$h"; \
|
||||||
|
done; \
|
||||||
|
printf '\nPKI et DNS debout : le reste de la flotte peut monter.\n'
|
||||||
|
|
||||||
myDay: reconstruire ## Alias strict de `reconstruire` (meme cible, memes gardes)
|
myDay: reconstruire ## Alias strict de `reconstruire` (meme cible, memes gardes)
|
||||||
|
|
||||||
deployer-groupe: ## Deploie un seul groupe sur toute la flotte — GROUPE=<groupe>
|
deployer-groupe: ## Deploie un seul groupe sur toute la flotte — GROUPE=<groupe>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 36 scripts expliques et atteignables, 87 cibles make documentees, 54 roles avec README. |
|
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 37 scripts expliques et atteignables, 87 cibles make documentees, 54 roles avec README. |
|
||||||
|
|
||||||
## Couverture des affirmations ✅ du registre
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ code + les README de rôles). Cette page comble ces deux trous.
|
||||||
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
|
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
|
||||||
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
|
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
|
||||||
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md` — **statique** : lit le dépôt, aucun appel réseau ; la conformité du déployé est l'affaire des devis de service (ligne au-dessus), `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
|
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md` — **statique** : lit le dépôt, aucun appel réseau ; la conformité du déployé est l'affaire des devis de service (ligne au-dessus), `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
|
||||||
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **67 décisions en vigueur** (D-01 → D-70, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **68 décisions en vigueur** (D-01 → D-71, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
||||||
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
|
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
|
||||||
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
|
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
|
||||||
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
|
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ sont les seules vérifiables.
|
||||||
| **D-68** | On **écrit, puis on relit et on compare** — quelle que soit l'interface ; on choisit celle dont le chemin de **lecture** parle le même langage que le chemin d'**écriture** | « toujours préférer l'API » n'aurait prédit aucune des pannes du 2026-08-08 : sur six familles de défauts, deux venaient d'un CLI, une d'un module Ansible (`ldap_entry` crée sans jamais modifier), une d'un `grep` de fichier, une de la précédence Ansible, une de mon comparateur. Le facteur commun est d'avoir écrit sans relire. Et la plupart de la flotte n'a **pas** d'API — Postfix, Dovecot, nginx, slapd, nftables : `postconf -h` / `postconf -e` sont symétriques, c'est tout ce qu'on demande | `devis-services.md` | les 5 devis |
|
| **D-68** | On **écrit, puis on relit et on compare** — quelle que soit l'interface ; on choisit celle dont le chemin de **lecture** parle le même langage que le chemin d'**écriture** | « toujours préférer l'API » n'aurait prédit aucune des pannes du 2026-08-08 : sur six familles de défauts, deux venaient d'un CLI, une d'un module Ansible (`ldap_entry` crée sans jamais modifier), une d'un `grep` de fichier, une de la précédence Ansible, une de mon comparateur. Le facteur commun est d'avoir écrit sans relire. Et la plupart de la flotte n'a **pas** d'API — Postfix, Dovecot, nginx, slapd, nftables : `postconf -h` / `postconf -e` sont symétriques, c'est tout ce qu'on demande | `devis-services.md` | les 5 devis |
|
||||||
| **D-69** | Sur Keycloak : **l'API pour toute map ou collection** (`smtpServer`, `attributes`, `config`), `kcadm` pour les scalaires et les créations | `kcadm -s` sur une map accepte la commande, **sort en succès et n'écrit rien** — mesuré deux fois le 2026-08-08 (`smtpServer` resté vide après deux déploiements verts, puis `post.logout.redirect.uris`). Le CLI reste préféré ailleurs : c'est le vocabulaire de la documentation du produit, donc lisible sans IA | `roles/serveur_keycloak/tasks/` | `make identite-plan` |
|
| **D-69** | Sur Keycloak : **l'API pour toute map ou collection** (`smtpServer`, `attributes`, `config`), `kcadm` pour les scalaires et les créations | `kcadm -s` sur une map accepte la commande, **sort en succès et n'écrit rien** — mesuré deux fois le 2026-08-08 (`smtpServer` resté vide après deux déploiements verts, puis `post.logout.redirect.uris`). Le CLI reste préféré ailleurs : c'est le vocabulaire de la documentation du produit, donc lisible sans IA | `roles/serveur_keycloak/tasks/` | `make identite-plan` |
|
||||||
| **D-70** | La documentation **dit et explique tout ce que le dépôt fait** — et l'exigence est **outillée**, pas seulement énoncée | une exigence qu'on n'outille pas pourrit en silence : la carte annonçait « 28 décisions » quand il y en avait 66, et disait les accès « non construits » alors qu'ils tournaient en production. **P31** garde le couvert — chaque script s'explique et reste atteignable, chaque cible `make` porte son aide (sauf les internes préfixées `_`), chaque rôle a son README. Elle ne garde **pas** la qualité du « pourquoi » : ça se juge en revue, et ça vit dans `CHANGELOG.md` et ici | `devis-services.md`, `CHANGELOG.md` | **P31** |
|
| **D-70** | La documentation **dit et explique tout ce que le dépôt fait** — et l'exigence est **outillée**, pas seulement énoncée | une exigence qu'on n'outille pas pourrit en silence : la carte annonçait « 28 décisions » quand il y en avait 66, et disait les accès « non construits » alors qu'ils tournaient en production. **P31** garde le couvert — chaque script s'explique et reste atteignable, chaque cible `make` porte son aide (sauf les internes préfixées `_`), chaque rôle a son README. Elle ne garde **pas** la qualité du « pourquoi » : ça se juge en revue, et ça vit dans `CHANGELOG.md` et ici | `devis-services.md`, `CHANGELOG.md` | **P31** |
|
||||||
|
| **D-71** | **Une PKI et un DNS fonctionnels avant toute chose** ; puis, par VM : socle → enrôlement PKI → enregistrement DNS (A **et** PTR) | `deployer-tout` déroule par COUCHES — correct, mais chaque VM réclame alors un certificat à une autorité pas encore debout, et l'échec se lit comme un défaut du rôle et non d'ordre. Les deux hôtes d'amorçage se **dérivent** de `applications.<step_ca\|powerdns>.hote` : déplacer l'autorité déplace l'amorçage. **Deux exceptions structurelles assumées** — l'AC s'auto-signe, le DNS pose son propre enregistrement | `Makefile` `_amorcer-socle`, `scripts/socle_amorcage.py` | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
serveur_powerdns_packages:
|
||||||
|
- pdns-server
|
||||||
|
- pdns-backend-bind
|
||||||
|
- bind9-utils
|
||||||
|
- dnsutils
|
||||||
|
|
||||||
|
serveur_powerdns_service_name: "pdns"
|
||||||
|
serveur_powerdns_zone_directory: "/etc/powerdns/zones"
|
||||||
|
serveur_powerdns_bind_config: "/etc/powerdns/bindbackend.conf"
|
||||||
|
serveur_powerdns_zone: "{{ domaine_interne }}"
|
||||||
|
serveur_powerdns_nameserver: "ns1"
|
||||||
|
serveur_powerdns_contact: "hostmaster.{{ domaine_interne }}"
|
||||||
|
serveur_powerdns_ttl: 3600
|
||||||
|
serveur_powerdns_serial: 2026062201
|
||||||
|
serveur_powerdns_soa_refresh: 3600
|
||||||
|
serveur_powerdns_soa_retry: 900
|
||||||
|
serveur_powerdns_soa_expire: 1209600
|
||||||
|
serveur_powerdns_soa_minimum: 3600
|
||||||
|
# L'adresse du tenant SEULEMENT, pas `0.0.0.0` : lier toutes les interfaces occupe aussi
|
||||||
|
# `127.0.0.1:53`, ou un resolveur recursif local voudrait s'installer. Ce n'est pas un
|
||||||
|
# probleme theorique — c'est la raison pour laquelle `client_unbound` exempte cet hote.
|
||||||
|
# PowerDNS reste autoritatif pur : il repond pour la zone souveraine et REFUSE le reste.
|
||||||
|
serveur_powerdns_listen_addresses:
|
||||||
|
- "{{ ansible_host | default('0.0.0.0') }}"
|
||||||
|
serveur_powerdns_allow_axfr_ips: []
|
||||||
|
|
||||||
|
# Enregistrements additionnels geres explicitement.
|
||||||
|
serveur_powerdns_records:
|
||||||
|
- name: "dns"
|
||||||
|
type: "CNAME"
|
||||||
|
value: "ns1.{{ serveur_powerdns_zone }}."
|
||||||
|
|
||||||
|
# Groupes dont les hotes actifs avec ansible_host seront ajoutes a la zone.
|
||||||
|
serveur_powerdns_inventory_groups:
|
||||||
|
- hotes_actifs
|
||||||
|
|
||||||
|
# Publier des A pour les FQDN d'exposition (champ 'expose' des applications) vers l'edge
|
||||||
|
# qui les sert (domaines.edge). Ferme la boucle : declarer 'expose' -> vhost + cert + DNS.
|
||||||
|
serveur_powerdns_publier_expositions: true
|
||||||
|
serveur_powerdns_expositions: []
|
||||||
|
|
||||||
|
# Zone INVERSE — DERIVEE du supernet, jamais ecrite. Un /16 `10.(10+index).0.0` donne
|
||||||
|
# `(10+index).10.in-addr.arpa`. Vide si le supernet n'est pas connu ou n'est pas un /16 :
|
||||||
|
# mieux vaut pas de zone inverse qu'une zone fausse.
|
||||||
|
#
|
||||||
|
# Pourquoi elle manquait. Rien dans le moteur ne touchait `in-addr.arpa` — mesure du
|
||||||
|
# 2026-08-08. Un hote pouvait donc etre resolu par son nom et rester anonyme a l'envers,
|
||||||
|
# ce que la plupart des services de courriel et de journalisation reprochent en silence.
|
||||||
|
serveur_powerdns_zone_inverse: >-
|
||||||
|
{{ (setops_supernet | default('') | ansible.utils.ipaddr('prefix') | int == 16)
|
||||||
|
| ternary(
|
||||||
|
(setops_supernet | default('0.0.0.0/0')).split('.')[1] ~ '.'
|
||||||
|
~ (setops_supernet | default('0.0.0.0/0')).split('.')[0] ~ '.in-addr.arpa',
|
||||||
|
'') }}
|
||||||
|
|
@ -39,3 +39,17 @@ serveur_powerdns_inventory_groups:
|
||||||
# qui les sert (domaines.edge). Ferme la boucle : declarer 'expose' -> vhost + cert + DNS.
|
# qui les sert (domaines.edge). Ferme la boucle : declarer 'expose' -> vhost + cert + DNS.
|
||||||
serveur_powerdns_publier_expositions: true
|
serveur_powerdns_publier_expositions: true
|
||||||
serveur_powerdns_expositions: []
|
serveur_powerdns_expositions: []
|
||||||
|
|
||||||
|
# Zone INVERSE — DERIVEE du supernet, jamais ecrite. Un /16 `10.(10+index).0.0` donne
|
||||||
|
# `(10+index).10.in-addr.arpa`. Vide si le supernet n'est pas connu ou n'est pas un /16 :
|
||||||
|
# mieux vaut pas de zone inverse qu'une zone fausse.
|
||||||
|
#
|
||||||
|
# Pourquoi elle manquait. Rien dans le moteur ne touchait `in-addr.arpa` — mesure du
|
||||||
|
# 2026-08-08. Un hote pouvait donc etre resolu par son nom et rester anonyme a l'envers,
|
||||||
|
# ce que la plupart des services de courriel et de journalisation reprochent en silence.
|
||||||
|
serveur_powerdns_zone_inverse: >-
|
||||||
|
{{ (setops_supernet | default('') | ansible.utils.ipaddr('prefix') | int == 16)
|
||||||
|
| ternary(
|
||||||
|
(setops_supernet | default('0.0.0.0/0')).split('.')[1] ~ '.'
|
||||||
|
~ (setops_supernet | default('0.0.0.0/0')).split('.')[0] ~ '.in-addr.arpa',
|
||||||
|
'') }}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,18 @@
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
notify: Validate and reload PowerDNS
|
notify: Validate and reload PowerDNS
|
||||||
|
|
||||||
|
# Meme source que la zone directe : `hotes_actifs` et son `ansible_host`. Deux zones
|
||||||
|
# alimentees par une seule verite — l'endroit ou elles divergeraient n'existe pas.
|
||||||
|
- name: Générer la zone DNS inverse depuis l'inventaire
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: zone-inverse.db.j2
|
||||||
|
dest: "{{ serveur_powerdns_zone_directory }}/{{ serveur_powerdns_zone_inverse }}.zone"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
when: serveur_powerdns_zone_inverse | length > 0
|
||||||
|
notify: Validate and reload PowerDNS
|
||||||
|
|
||||||
- name: Activer et démarrer PowerDNS
|
- name: Activer et démarrer PowerDNS
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: "{{ serveur_powerdns_service_name }}"
|
name: "{{ serveur_powerdns_service_name }}"
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,9 @@ zone "{{ serveur_powerdns_zone }}" {
|
||||||
type master;
|
type master;
|
||||||
file "{{ serveur_powerdns_zone_directory }}/{{ serveur_powerdns_zone }}.zone";
|
file "{{ serveur_powerdns_zone_directory }}/{{ serveur_powerdns_zone }}.zone";
|
||||||
};
|
};
|
||||||
|
{% if serveur_powerdns_zone_inverse %}
|
||||||
|
zone "{{ serveur_powerdns_zone_inverse }}" {
|
||||||
|
type master;
|
||||||
|
file "{{ serveur_powerdns_zone_directory }}/{{ serveur_powerdns_zone_inverse }}.zone";
|
||||||
|
};
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
24
roles/serveur_powerdns/templates/zone-inverse.db.j2
Normal file
24
roles/serveur_powerdns/templates/zone-inverse.db.j2
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
; Zone INVERSE — generee par Set-OPS (role serveur_powerdns). Ne pas editer a la main.
|
||||||
|
;
|
||||||
|
; Le nom de la zone est DERIVE du supernet du tenant, comme tout le reste : un /16
|
||||||
|
; 10.(10+index).0.0 donne (10+index).10.in-addr.arpa. Rien n'est ecrit.
|
||||||
|
;
|
||||||
|
; Les PTR viennent de la MEME source que les A — `hotes_actifs` et son `ansible_host`.
|
||||||
|
; Deux zones alimentees par une seule verite : c'est ce qui empeche l'endroit ou elles
|
||||||
|
; divergent d'exister.
|
||||||
|
$TTL {{ serveur_powerdns_ttl }}
|
||||||
|
@ IN SOA {{ serveur_powerdns_nameserver }}.{{ serveur_powerdns_zone }}. {{ serveur_powerdns_contact }}. (
|
||||||
|
{{ serveur_powerdns_serial }} ; serial
|
||||||
|
{{ serveur_powerdns_soa_refresh }} ; refresh
|
||||||
|
{{ serveur_powerdns_soa_retry }} ; retry
|
||||||
|
{{ serveur_powerdns_soa_expire }} ; expire
|
||||||
|
{{ serveur_powerdns_soa_minimum }} ) ; minimum
|
||||||
|
|
||||||
|
@ IN NS {{ serveur_powerdns_nameserver }}.{{ serveur_powerdns_zone }}.
|
||||||
|
|
||||||
|
{% for host_name in groups.get('hotes_actifs', []) | sort %}
|
||||||
|
{% set ip = hostvars[host_name].ansible_host | default('') %}
|
||||||
|
{% if ip %}
|
||||||
|
{{ ip.split('.')[3] }}.{{ ip.split('.')[2] }} IN PTR {{ host_name }}.{{ serveur_powerdns_zone }}.
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
53
scripts/socle_amorcage.py
Executable file
53
scripts/socle_amorcage.py
Executable file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Nomme les hotes qui doivent etre DEBOUT avant tous les autres, derives du plan.
|
||||||
|
|
||||||
|
Contrainte d'architecture posee le 2026-08-08 : « une PKI et un DNS fonctionnels avant
|
||||||
|
toute chose ». Sans eux, chaque VM qui monte reclame un certificat a une autorite absente
|
||||||
|
et un nom a un annuaire muet — et l'echec se lit comme un defaut du role, pas comme un
|
||||||
|
defaut d'ordre.
|
||||||
|
|
||||||
|
Les hotes ne sont PAS ecrits : ils se derivent de `applications.<app>.hote`. Deplacer
|
||||||
|
l'autorite dans le plan deplace l'amorcage avec elle.
|
||||||
|
|
||||||
|
python3 scripts/socle_amorcage.py # un nom d'hote par ligne, dans l'ordre
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
RACINE = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# L'ordre compte et n'est pas alphabetique : le DNS a besoin d'un certificat, l'autorite
|
||||||
|
# n'a besoin de personne. Elle passe donc devant — c'est la seule dependance reelle
|
||||||
|
# entre les deux.
|
||||||
|
APPLICATIONS = ["step_ca", "powerdns"]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
base = Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance"))
|
||||||
|
plan = base / "plan" / "applications.yml"
|
||||||
|
if not plan.exists():
|
||||||
|
print(f"Plan introuvable : {plan}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
apps = (yaml.safe_load(plan.read_text(encoding="utf-8")) or {}).get("applications") or {}
|
||||||
|
|
||||||
|
vus, sortie = set(), []
|
||||||
|
for nom in APPLICATIONS:
|
||||||
|
hote = (apps.get(nom) or {}).get("hote")
|
||||||
|
if not hote:
|
||||||
|
print(f"L'application « {nom} » n'est pas au plan : impossible d'amorcer le "
|
||||||
|
f"socle sans elle.", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
if hote not in vus: # PKI et DNS peuvent partager un hote
|
||||||
|
vus.add(hote)
|
||||||
|
sortie.append(hote)
|
||||||
|
print("\n".join(sortie))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Reference in a new issue