dns : le site resout chez lui, et le socle cesse de le defaire
Mise au point de l'exploitant : la frontiere n'a pas de service DNS actif et gere. Un
Unbound y tourne — il repondait, ce qui m'a induit en erreur — mais un processus n'est pas
un service. La delegation de zone que j'y avais posee est retiree.
Et le site a son propre DNS : `dns_amorcage` pointait encore sur la frontiere, valeur d'un
moment ou site-dns-01 n'existait pas. Elle vaut desormais 10.0.3.51.
Deux defauts revelés au passage :
- devis_opnsense lisait encore underlay.machines(), vide depuis le deplacement du plan.
Il proposait de RETIRER 36 objets — tous les alias et regles du site. Aucune preuve ne
couvre ce devis : c'est le plan avant application qui l'a attrape.
- le socle defaisait la bascule de client_resolveur a chaque passage. Sa garde ne
protegeait que l'hote du resolveur (127.0.0.1). Invisible chez un tenant, ou
dns_amorcage vaut l'adresse du resolveur : la coincidence masquait le defaut. Ca ne
s'est vu qu'en retirant la regle de pare-feu — les cinq machines ont perdu la
resolution d'un coup.
L'amorcage ne s'applique plus que si rien de sense n'est en place. Verifie : socle
`changed=0`, les cinq machines restent sur 10.0.3.51.
42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b60946f19f
commit
ea67b75a15
4 changed files with 106 additions and 5 deletions
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -1,5 +1,43 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-25 — Le site résout chez lui, et le socle cesse de le défaire
|
||||||
|
|
||||||
|
**Mise au point de l'exploitant** : la frontière OPNsense n'a pas de service DNS actif et
|
||||||
|
géré. Un Unbound y *tourne* — il répondait, ce qui m'a induit en erreur — mais un
|
||||||
|
processus n'est pas un service. La délégation de zone que j'y avais posée est retirée :
|
||||||
|
on ne règle pas ce que rien ne déclare ni ne prouve.
|
||||||
|
|
||||||
|
**Et surtout, le site a son propre DNS.** `dns_amorcage` pointait encore sur la frontière
|
||||||
|
(`10.0.3.1`), valeur d'un moment où `site-dns-01` n'existait pas. Elle a survécu à sa
|
||||||
|
raison d'être. Il vaut désormais `10.0.3.51`.
|
||||||
|
|
||||||
|
*Reste un cas étroit, nommé plutôt que résolu : la machine qui PORTE le DNS ne peut pas
|
||||||
|
résoudre chez elle avant de l'avoir installé. Un site bâti depuis zéro doit créer et
|
||||||
|
déployer `site-dns-01` en premier.*
|
||||||
|
|
||||||
|
### Le devis de la frontière ne voyait plus le site
|
||||||
|
|
||||||
|
En déplaçant le plan hors de `underlay.yml`, j'ai vidé `machines()` sans rebrancher
|
||||||
|
`devis_opnsense`. Il proposait de **retirer 36 objets** — tous les alias et toutes les
|
||||||
|
règles du site. Aucune preuve ne couvre le devis de la frontière du site, donc
|
||||||
|
`make prouver` restait vert : **c'est le plan avant application qui l'a attrapé**, et rien
|
||||||
|
d'autre ne l'aurait fait.
|
||||||
|
|
||||||
|
### Le socle défaisait la bascule du résolveur
|
||||||
|
|
||||||
|
Sa garde ne protégeait que l'hôte du résolveur (`127.0.0.1`). Partout ailleurs il
|
||||||
|
réécrivait `/etc/resolv.conf` avec `dns_amorcage`, annulant `client_resolveur` à chaque
|
||||||
|
passage.
|
||||||
|
|
||||||
|
Invisible chez un tenant : `dns_amorcage` y vaut l'adresse du résolveur du tenant, donc
|
||||||
|
réécrire remettait la même valeur. **La coïncidence masquait le défaut.** Sur le site les
|
||||||
|
deux ont divergé quelques heures — et ça ne s'est vu qu'en retirant la règle de pare-feu
|
||||||
|
vers la frontière : les cinq machines ont perdu la résolution d'un coup.
|
||||||
|
|
||||||
|
L'amorçage ne s'applique plus que si rien de sensé n'est en place : ni la loopback, ni un
|
||||||
|
résolveur déclaré de l'écosystème. Vérifié — le socle repasse `changed=0` et les cinq
|
||||||
|
machines restent sur `10.0.3.51`.
|
||||||
|
|
||||||
## 2026-08-25 — Le génome vit sur la forge du SITE
|
## 2026-08-25 — Le génome vit sur la forge du SITE
|
||||||
|
|
||||||
Six dépôts, 606 commits, chaque tête confrontée entre le poste et la forge : **identique**.
|
Six dépôts, 606 commits, chaque tête confrontée entre le poste et la forge : **identique**.
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
|
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
|
||||||
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
|
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
|
||||||
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 8 reseau(x), aucune collision avec la plage tenant. |
|
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 8 reseau(x), aucune collision avec la plage tenant. |
|
||||||
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 65 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,10.29.19.41/32,192.168.254.2/32,192.168.255.2/32. |
|
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 86 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,10.29.19.41/32,192.168.254.2/32,192.168.255.2/32. |
|
||||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 79 regle(s). |
|
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 79 regle(s). |
|
||||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
|
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
|
||||||
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,36 @@
|
||||||
{% for serveur in dns_amorcage.split(',') if serveur | trim %}
|
{% for serveur in dns_amorcage.split(',') if serveur | trim %}
|
||||||
nameserver {{ serveur | trim }}
|
nameserver {{ serveur | trim }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
# L'AMORÇAGE NE DOIT PAS DÉFAIRE CE QUI EST DÉJÀ EN PLACE (2026-08-25).
|
||||||
|
#
|
||||||
|
# La garde ne protégeait que l'hôte du résolveur lui-même (`127.0.0.1`). Partout
|
||||||
|
# ailleurs, le socle réécrivait `/etc/resolv.conf` avec `dns_amorcage` — donc
|
||||||
|
# **défaisait la bascule** de `client_resolveur` à chaque passage.
|
||||||
|
#
|
||||||
|
# Ça ne s'était jamais vu chez un tenant : `dns_amorcage` y vaut l'adresse du
|
||||||
|
# résolveur du tenant, si bien que réécrire remettait la même valeur. La
|
||||||
|
# coïncidence masquait le défaut.
|
||||||
|
#
|
||||||
|
# Sur le site, les deux ont divergé quelques heures — l'amorçage pointait encore sur
|
||||||
|
# la frontière alors que `client_resolveur` avait basculé les machines sur
|
||||||
|
# `site-dns-01`. Chaque déploiement du socle les ramenait en arrière, en silence, et
|
||||||
|
# ça ne s'est vu qu'en retirant la règle de pare-feu vers la frontière : les cinq
|
||||||
|
# machines ont alors perdu la résolution d'un coup.
|
||||||
|
#
|
||||||
|
# On ne pose donc l'amorçage que si rien de sensé n'est déjà là : ni la loopback, ni
|
||||||
|
# un résolveur déclaré de cet écosystème.
|
||||||
|
vars:
|
||||||
|
serveur_debian_resolv_texte: >-
|
||||||
|
{{ serveur_debian_resolv_actuel.content | default('') | b64decode | string }}
|
||||||
|
serveur_debian_resolveurs_declares: >-
|
||||||
|
{{ (groups['serveur_resolveur'] | default([]))
|
||||||
|
| map('extract', hostvars, 'ansible_host') | select | list }}
|
||||||
when:
|
when:
|
||||||
- dns_amorcage is defined
|
- dns_amorcage is defined
|
||||||
- dns_amorcage | string | length > 0
|
- dns_amorcage | string | length > 0
|
||||||
- "'127.0.0.1' not in (serveur_debian_resolv_actuel.content | default('') | b64decode | string)"
|
- "'127.0.0.1' not in serveur_debian_resolv_texte"
|
||||||
|
- serveur_debian_resolveurs_declares
|
||||||
|
| select('in', serveur_debian_resolv_texte) | list | length == 0
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- common_packages
|
- common_packages
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,35 @@ def _instance_active() -> tuple[str, dict] | tuple[None, None]:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def _plan_site() -> Path | None:
|
||||||
|
"""Le `plan/` du SITE, a cote de son `underlay.yml`."""
|
||||||
|
c = underlay_mod.chemin()
|
||||||
|
return (c.resolve().parent / "plan") if c else None
|
||||||
|
|
||||||
|
|
||||||
|
def _lire_plan_site(nom: str) -> dict:
|
||||||
|
d = _plan_site()
|
||||||
|
f = (d / nom) if d else None
|
||||||
|
if not f or not f.is_file():
|
||||||
|
return {}
|
||||||
|
return yaml.safe_load(f.read_text(encoding="utf-8")) or {}
|
||||||
|
|
||||||
|
|
||||||
|
def _machines_du_plan_site() -> list[dict]:
|
||||||
|
srv = (_lire_plan_site("serveurs.yml") or {}).get("serveurs") or {}
|
||||||
|
return [{**s, "nom": nom} for nom, s in srv.items()
|
||||||
|
if str(s.get("etat", "actif")) == "actif"]
|
||||||
|
|
||||||
|
|
||||||
|
def _services_du_plan_site() -> dict[str, list[str]]:
|
||||||
|
apps = (_lire_plan_site("applications.yml") or {}).get("applications") or {}
|
||||||
|
out: dict[str, list[str]] = {}
|
||||||
|
for app in apps.values():
|
||||||
|
if app.get("hote") and app.get("groupe"):
|
||||||
|
out.setdefault(str(app["hote"]), []).append(str(app["groupe"]))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
"""Le devis, en structure — rendue ensuite en texte ou en JSON."""
|
"""Le devis, en structure — rendue ensuite en texte ou en JSON."""
|
||||||
flux_par_role = charger_flux()
|
flux_par_role = charger_flux()
|
||||||
|
|
@ -558,7 +587,15 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
# Une regle posee sur la mauvaise interface ne correspond jamais — c'est la panne la
|
# Une regle posee sur la mauvaise interface ne correspond jamais — c'est la panne la
|
||||||
# plus silencieuse de cette couche.
|
# plus silencieuse de cette couche.
|
||||||
_u = underlay_mod.charger()
|
_u = underlay_mod.charger()
|
||||||
_machines_site = underlay_mod.machines(_u)
|
# LES MACHINES DU SITE VIENNENT DE SON PLAN, plus de l'underlay (2026-08-25).
|
||||||
|
#
|
||||||
|
# En deplacant le plan hors de `underlay.yml`, j'ai vide `machines()` sans reprendre
|
||||||
|
# ici : le devis a cesse de voir le site et a propose de RETIRER ses 36 objets — tous
|
||||||
|
# ses alias et toutes ses regles. Aucune preuve ne couvre le devis de la frontiere du
|
||||||
|
# site, donc `make prouver` restait vert. C'est le plan avant application qui l'a
|
||||||
|
# attrape, et rien d'autre ne l'aurait fait.
|
||||||
|
_machines_site = _machines_du_plan_site()
|
||||||
|
_services_site = _services_du_plan_site()
|
||||||
if _machines_site:
|
if _machines_site:
|
||||||
_res_site = {r.get("nom"): r for r in underlay_mod.reseaux(_u)}
|
_res_site = {r.get("nom"): r for r in underlay_mod.reseaux(_u)}
|
||||||
_sous_site = sorted({str(_res_site[m["reseau"]]["sous_reseau"])
|
_sous_site = sorted({str(_res_site[m["reseau"]]["sous_reseau"])
|
||||||
|
|
@ -629,12 +666,12 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
"passerelle : le site n'a pas de resolveur a lui.",
|
"passerelle : le site n'a pas de resolveur a lui.",
|
||||||
})
|
})
|
||||||
|
|
||||||
_roles_site = sorted({s for m in _machines_site for s in (m.get("services") or [])}
|
_roles_site = sorted({g for gs in _services_site.values() for g in gs}
|
||||||
| {"serveur_debian"})
|
| {"serveur_debian"})
|
||||||
for _role in _roles_site:
|
for _role in _roles_site:
|
||||||
_ips = sorted({str(m["ip"]) for m in _machines_site
|
_ips = sorted({str(m["ip"]) for m in _machines_site
|
||||||
if _role == "serveur_debian"
|
if _role == "serveur_debian"
|
||||||
or _role in (m.get("services") or [])})
|
or _role in _services_site.get(m["nom"], [])})
|
||||||
_a = nom_alias(_role, "SITE")
|
_a = nom_alias(_role, "SITE")
|
||||||
alias[_a] = {
|
alias[_a] = {
|
||||||
"type": "host",
|
"type": "host",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue