inter-sites : le lien entre les sites, et les pattes de tunnel hors des pattes de zone
- underlay.py : role d'hote frontiere_pair ; reseaux_tunnel() ecarte les tunnels des pattes de la frontiere (reseaux de gestion, SETOPS_FABRIC, SETOPS_FRONTIERE, resoudre_flux, supervision des pattes). - devis_opnsense : seuls les roles des machines du plan font un alias (le relais en faisait un vide). - La machine du cache recoit artefacts_amorcage_direct au lieu d'une valeur vide : le vide ouvrait la sortie 80/443 du socle a toutes les machines du site (jamais applique). P79 revient a sa forme stricte. 95 preuves (make verifier), make test passe. CHANGELOG 148. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ca731eedc4
commit
65a4268871
7 changed files with 90 additions and 31 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -1,3 +1,35 @@
|
||||||
|
## 2026-10-14 (148) — Le lien entre les sites, et ce qu'il a montré en ne devant rien ouvrir
|
||||||
|
|
||||||
|
**95 preuves.** `make verifier` passe (site de Chezlepro), `make test` passe. Témoin : avec le lien
|
||||||
|
déclaré dans sa carte, le devis de la frontière de Chezlepro est **identique au caractère près**
|
||||||
|
à celui d'avant-hier, et son inventaire ne diffère que par le drapeau du cache ; les fiches
|
||||||
|
remises aux locataires ne changent que par l'empreinte de la carte.
|
||||||
|
|
||||||
|
**Le tunnel entre les sites (phase 1).** L'ancien tunnel `bifrost-1` ↔ site de TechnoLibre était
|
||||||
|
mort depuis le remplacement du boîtier, et ses adresses autorisées (`10.23.0.0/16`, l'ancien
|
||||||
|
index du site) recouvraient l'écosystème OPS-Technolibre hébergé à Chezlepro. Refait :
|
||||||
|
`requin` l'initie (DHCP derrière une traduction d'adresse) vers `69.70.26.62:51820` ; chaque
|
||||||
|
bout n'admet que l'autre site et les deux primaires DNS des locataires ; routes WireGuard
|
||||||
|
désactivées des deux côtés (la route `10.23.0.0/16` de `bifrost-1` a disparu) ; interface
|
||||||
|
`INTERSITES` sans règle sur `requin` — rien ne passe encore. Prouvé : poignée de main des deux
|
||||||
|
côtés, compteurs qui montent. Déclaré dans les deux cartes (réseau `inter-sites`, rôle d'hôte
|
||||||
|
`frontiere_pair`). Sur `bifrost-1`, enregistrer l'instance avec un seul champ a vidé son lien
|
||||||
|
vers le pair (l'API remet à vide les relations absentes) : vu au relevé suivant, rétabli.
|
||||||
|
|
||||||
|
**Une régression de l'entrée 146, attrapée avant toute application.** La valeur VIDE
|
||||||
|
d'`artefacts_amorcage` (machine du cache) activait la sortie conditionnelle 80/443 de
|
||||||
|
`serveur_debian` — que le devis ouvre pour tout le groupe, pas pour la machine qui remplit la
|
||||||
|
condition : toutes les machines du site seraient sorties sur l'Internet. Ni `requin` ni
|
||||||
|
`bifrost-1` n'ont reçu ce devis. Remplacé par un drapeau à part, `artefacts_amorcage_direct` ;
|
||||||
|
P79 revient à sa forme stricte.
|
||||||
|
|
||||||
|
**Les pattes de tunnel ne sont pas des pattes de zone.** Le relais puis le lien ont donné à la
|
||||||
|
frontière des adresses qui devenaient sources admises des journaux et des métriques — en
|
||||||
|
désaccord avec les règles des locataires. `underlay.reseaux_tunnel()` les écarte des six
|
||||||
|
dérivations : réseaux de gestion, `SETOPS_FABRIC`, `SETOPS_FRONTIERE`, fabric et frontière dans
|
||||||
|
`resoudre_flux`, supervision des pattes. Et le devis ne retient plus que les rôles des machines
|
||||||
|
du PLAN : `serveur_relais`, porté par le seul relais, y faisait un alias vide.
|
||||||
|
|
||||||
## 2026-10-14 (147) — L'ordre d'un site neuf : le dépôt avant l'autorité, le cache avant tous
|
## 2026-10-14 (147) — L'ordre d'un site neuf : le dépôt avant l'autorité, le cache avant tous
|
||||||
|
|
||||||
**95 preuves.** `make verifier` passe (site de Chezlepro), `make test` passe. Simulation complète
|
**95 preuves.** `make verifier` passe (site de Chezlepro), `make test` passe. Simulation complète
|
||||||
|
|
|
||||||
|
|
@ -78,18 +78,16 @@
|
||||||
when:
|
when:
|
||||||
- artefacts_amorcage is defined
|
- artefacts_amorcage is defined
|
||||||
- artefacts_amorcage | string | length > 0
|
- artefacts_amorcage | string | length > 0
|
||||||
|
- not (artefacts_amorcage_direct | default(false) | bool)
|
||||||
|
|
||||||
# LA MACHINE QUI PORTE LE CACHE VA DROIT CHEZ DEBIAN (2026-10-13). L'inventaire lui
|
# LA MACHINE QUI PORTE LE CACHE VA DROIT CHEZ DEBIAN (2026-10-13). Le fichier herite du
|
||||||
# donne `artefacts_amorcage` VIDE (et non absent) : le fichier herite du gabarit vise
|
# gabarit vise un cache qui n'existe pas encore — le sien — ou une adresse perimee :
|
||||||
# un cache qui n'existe pas encore — le sien — ou une adresse perimee. Absent, il
|
# l'inventaire le dit par `artefacts_amorcage_direct`, et on le retire.
|
||||||
# survivrait ; vide, on le retire.
|
|
||||||
- name: Retirer l'amorcage sur la machine qui porte le cache
|
- name: Retirer l'amorcage sur la machine qui porte le cache
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /etc/apt/apt.conf.d/00-setops-amorcage-artefacts
|
path: /etc/apt/apt.conf.d/00-setops-amorcage-artefacts
|
||||||
state: absent
|
state: absent
|
||||||
when:
|
when: artefacts_amorcage_direct | default(false) | bool
|
||||||
- artefacts_amorcage is defined
|
|
||||||
- artefacts_amorcage | string | length == 0
|
|
||||||
|
|
||||||
# RETIRER CE QUI DESIGNE UN CACHE QUI N'EXISTE PLUS (2026-09-10).
|
# RETIRER CE QUI DESIGNE UN CACHE QUI N'EXISTE PLUS (2026-09-10).
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,8 @@ def reseaux_locaux_frontiere(underlay: dict | None, transit: dict | None) -> lis
|
||||||
sur = {str(h.get("reseau")) for h in underlay_mod.hotes(underlay)
|
sur = {str(h.get("reseau")) for h in underlay_mod.hotes(underlay)
|
||||||
if str(h.get("nom") or "") == nom}
|
if str(h.get("nom") or "") == nom}
|
||||||
sur.discard(str((transit or {}).get("nom")))
|
sur.discard(str((transit or {}).get("nom")))
|
||||||
|
# Un tunnel arrive par SON interface, jamais par la gestion (relais, lien entre sites).
|
||||||
|
sur -= underlay_mod.reseaux_tunnel(underlay)
|
||||||
cidrs = []
|
cidrs = []
|
||||||
for r in underlay_mod.reseaux(underlay):
|
for r in underlay_mod.reseaux(underlay):
|
||||||
if str(r.get("nom")) in sur and r.get("sous_reseau"):
|
if str(r.get("nom")) in sur and r.get("sous_reseau"):
|
||||||
|
|
@ -907,7 +909,8 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
# par `!SETOPS_INTERNES` (la forme de « vers Internet ») l'aurait exclu tout en
|
# par `!SETOPS_INTERNES` (la forme de « vers Internet ») l'aurait exclu tout en
|
||||||
# ayant l'air d'ouvrir le flux. D'ou le mot `fabric`, et cet alias-ci.
|
# ayant l'air d'ouvrir le flux. D'ou le mot `fabric`, et cet alias-ci.
|
||||||
_fabric = sorted({str(h["ip"]) for h in underlay_mod.hotes(_u)
|
_fabric = sorted({str(h["ip"]) for h in underlay_mod.hotes(_u)
|
||||||
if h.get("role") in ("hyperviseur", "frontiere") and h.get("ip")})
|
if h.get("role") in ("hyperviseur", "frontiere") and h.get("ip")
|
||||||
|
and str(h.get("reseau")) not in underlay_mod.reseaux_tunnel(_u)})
|
||||||
alias["SETOPS_FABRIC"] = {
|
alias["SETOPS_FABRIC"] = {
|
||||||
"type": "host",
|
"type": "host",
|
||||||
"contenu": _fabric,
|
"contenu": _fabric,
|
||||||
|
|
@ -1020,7 +1023,12 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
"description": "Autoritatifs des tenants heberges — source du DNS public",
|
"description": "Autoritatifs des tenants heberges — source du DNS public",
|
||||||
}
|
}
|
||||||
|
|
||||||
_roles_site = sorted({g for gs in _services_site.values() for g in gs}
|
# LES ROLES DES MACHINES DU PLAN, PAS DE TOUT L'INVENTAIRE (2026-10-14). L'inventaire du
|
||||||
|
# site compte aussi des EQUIPEMENTS hors du plan — les hyperviseurs, le relais —, mais
|
||||||
|
# les adresses des alias ne viennent que du plan : un role porte par le seul relais
|
||||||
|
# (`serveur_relais`) donnait un alias VIDE, et le devis refusait de se rendre.
|
||||||
|
_noms_plan = {str(m["nom"]) for m in _machines_site}
|
||||||
|
_roles_site = sorted({g for h, gs in _services_site.items() if h in _noms_plan 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
|
||||||
|
|
@ -1583,7 +1591,8 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
if _flux_frontiere:
|
if _flux_frontiere:
|
||||||
_pattes_frontiere = sorted({str(h["ip"]) for h in underlay_mod.hotes(_u)
|
_pattes_frontiere = sorted({str(h["ip"]) for h in underlay_mod.hotes(_u)
|
||||||
if h.get("role") == "frontiere" and h.get("ip")
|
if h.get("role") == "frontiere" and h.get("ip")
|
||||||
and str(h.get("etat", "actif")) != "reserve"})
|
and str(h.get("etat", "actif")) != "reserve"
|
||||||
|
and str(h.get("reseau")) not in underlay_mod.reseaux_tunnel(_u)})
|
||||||
if _pattes_frontiere:
|
if _pattes_frontiere:
|
||||||
alias["SETOPS_FRONTIERE"] = {
|
alias["SETOPS_FRONTIERE"] = {
|
||||||
"type": "host",
|
"type": "host",
|
||||||
|
|
|
||||||
|
|
@ -4079,19 +4079,10 @@ def _repli_hotes_du_site(enfants: dict, hv: dict) -> list[str]:
|
||||||
fautes.append(f"rechargement : {h} renouvelle son certificat sans recharger "
|
fautes.append(f"rechargement : {h} renouvelle son certificat sans recharger "
|
||||||
f"nginx — le nouveau reste sur disque, l'ancien est servi")
|
f"nginx — le nouveau reste sur disque, l'ancien est servi")
|
||||||
if membres("serveur_artefacts"):
|
if membres("serveur_artefacts"):
|
||||||
porteurs = set(membres("serveur_artefacts"))
|
|
||||||
for h, v in sorted(hv.items()):
|
for h, v in sorted(hv.items()):
|
||||||
v = v or {}
|
if (v or {}).get("dns_amorcage") and not (v or {}).get("artefacts_amorcage"):
|
||||||
if not v.get("dns_amorcage") or v.get("artefacts_amorcage"):
|
fautes.append(f"jumeaux : {h} recoit `dns_amorcage` sans "
|
||||||
continue
|
f"`artefacts_amorcage` — le gabarit garde son ancien proxy apt")
|
||||||
# VIDE SUR LA MACHINE QUI PORTE LE CACHE, ET SEULEMENT LA (2026-10-13). Elle ne
|
|
||||||
# peut pas s'amorcer sur un cache qu'elle n'a pas encore : le socle RETIRE alors
|
|
||||||
# le fichier du gabarit (`artefacts_amorcage` defini et vide). Absent, ou vide
|
|
||||||
# ailleurs, c'est toujours le repli silencieux que cette garde existe pour voir.
|
|
||||||
if h in porteurs and "artefacts_amorcage" in v:
|
|
||||||
continue
|
|
||||||
fautes.append(f"jumeaux : {h} recoit `dns_amorcage` sans "
|
|
||||||
f"`artefacts_amorcage` — le gabarit garde son ancien proxy apt")
|
|
||||||
return fautes
|
return fautes
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -368,8 +368,11 @@ def _adresses_fabric() -> list[str]:
|
||||||
carte = _u.charger()
|
carte = _u.charger()
|
||||||
if not carte:
|
if not carte:
|
||||||
return []
|
return []
|
||||||
|
# Meme exclusion que `SETOPS_FABRIC` : une patte de tunnel n'est pas du materiel
|
||||||
|
# que l'on joint (voir `underlay.reseaux_tunnel`).
|
||||||
return sorted({str(h["ip"]) for h in _u.hotes(carte)
|
return sorted({str(h["ip"]) for h in _u.hotes(carte)
|
||||||
if h.get("role") in ("hyperviseur", "frontiere") and h.get("ip")})
|
if h.get("role") in ("hyperviseur", "frontiere") and h.get("ip")
|
||||||
|
and str(h.get("reseau")) not in _u.reseaux_tunnel(carte)})
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -394,7 +397,8 @@ def _adresses_frontiere() -> list[str]:
|
||||||
return []
|
return []
|
||||||
return sorted({str(h["ip"]) for h in _u.hotes(carte)
|
return sorted({str(h["ip"]) for h in _u.hotes(carte)
|
||||||
if h.get("role") == "frontiere" and h.get("ip")
|
if h.get("role") == "frontiere" and h.get("ip")
|
||||||
and str(h.get("etat", "actif")) != "reserve"})
|
and str(h.get("etat", "actif")) != "reserve"
|
||||||
|
and str(h.get("reseau")) not in _u.reseaux_tunnel(carte)})
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -611,6 +611,10 @@ def inventaire() -> dict:
|
||||||
# est une alarme qu'on cesse de lire.
|
# est une alarme qu'on cesse de lire.
|
||||||
if str(_h.get("etat") or "actif") != "actif":
|
if str(_h.get("etat") or "actif") != "actif":
|
||||||
continue
|
continue
|
||||||
|
# Une patte de TUNNEL n'est pas une patte de zone : le relais et le lien entre les
|
||||||
|
# sites se surveillent par leur passerelle (RELAIS_GW), pas comme une zone.
|
||||||
|
if str(_h.get("reseau") or "") in U.reseaux_tunnel(u):
|
||||||
|
continue
|
||||||
_ip = str(_h.get("ip") or "")
|
_ip = str(_h.get("ip") or "")
|
||||||
_res = str(_h.get("reseau") or "")
|
_res = str(_h.get("reseau") or "")
|
||||||
if not _ip:
|
if not _ip:
|
||||||
|
|
@ -981,12 +985,16 @@ def inventaire() -> dict:
|
||||||
# LE CACHE NE S'AMORCE PAS SUR LUI-MEME (2026-10-13). Toute machine du site
|
# LE CACHE NE S'AMORCE PAS SUR LUI-MEME (2026-10-13). Toute machine du site
|
||||||
# recevait `artefacts_amorcage` = le cache — y compris le cache, et des le
|
# recevait `artefacts_amorcage` = le cache — y compris le cache, et des le
|
||||||
# socle, alors qu'il n'est deploye qu'a la couche `services`. Le premier `apt`
|
# socle, alors qu'il n'est deploye qu'a la couche `services`. Le premier `apt`
|
||||||
# d'un site construit depuis zero aurait echoue partout ; celui de Chezlepro
|
# d'un site construit depuis zero aurait echoue partout.
|
||||||
# n'a jamais ete reconstruit, son cache existait deja. VIDE ici, et non absent :
|
#
|
||||||
# le socle RETIRE alors le fichier du gabarit (qui vise `10.0.33.21`, une adresse
|
# UN DRAPEAU A PART, ET NON UNE VALEUR VIDE (corrige le 2026-10-14). Le vide
|
||||||
# d'avant le renumerotage) et la machine va droit chez Debian, ce que la
|
# activait la sortie conditionnelle 80/443 de `serveur_debian` (`seulement_si:
|
||||||
# frontiere permet au cache du site.
|
# artefacts_amorcage non_vide: false`) — et le devis l'ouvre pour TOUT le groupe,
|
||||||
**({"artefacts_amorcage": ""}
|
# pas pour la machine qui remplit la condition : toutes les machines du site
|
||||||
|
# seraient sorties sur l'Internet. Vu au devis avant toute application. Le cache
|
||||||
|
# garde sa valeur ; ce drapeau dit au socle de RETIRER le fichier du gabarit (qui
|
||||||
|
# vise `10.0.33.21`, d'avant le renumerotage). Il sort par sa propre regle.
|
||||||
|
**({"artefacts_amorcage_direct": True}
|
||||||
if _cache_site and str(srv.get("ip")) == _cache_site else {}),
|
if _cache_site and str(srv.get("ip")) == _cache_site else {}),
|
||||||
# En dernier : ce qu'une machine declare d'elle-meme prime sur ce que le
|
# En dernier : ce qu'une machine declare d'elle-meme prime sur ce que le
|
||||||
# plan declare pour toutes.
|
# plan declare pour toutes.
|
||||||
|
|
|
||||||
|
|
@ -434,7 +434,12 @@ def _ports_a_plat(hote: dict) -> list[str]:
|
||||||
# `serveur_relais`), et qui porte sa face publique. Le site de TechnoLibre n'a qu'une
|
# `serveur_relais`), et qui porte sa face publique. Le site de TechnoLibre n'a qu'une
|
||||||
# adresse DHCP ; ses adresses fixes sont louees sur un VPS et lui arrivent par un tunnel.
|
# adresse DHCP ; ses adresses fixes sont louees sur un VPS et lui arrivent par un tunnel.
|
||||||
# Ce n'est pas une `passerelle_amont` — celle-ci, nous ne la configurons jamais.
|
# Ce n'est pas une `passerelle_amont` — celle-ci, nous ne la configurons jamais.
|
||||||
ROLES_HOTE = ("switch", "hyperviseur", "frontiere", "passerelle_amont", "relais")
|
#
|
||||||
|
# `frontiere_pair` (2026-10-13) : la frontiere d'un AUTRE site, au bout du tunnel entre les
|
||||||
|
# sites. Declaree dans les deux cartes, chacune avec sa propre frontiere en face. Ce n'est
|
||||||
|
# pas la notre (aucun devis ne la configure d'ici) ; ce n'est pas un routeur qu'on ignore.
|
||||||
|
ROLES_HOTE = ("switch", "hyperviseur", "frontiere", "passerelle_amont", "relais",
|
||||||
|
"frontiere_pair")
|
||||||
|
|
||||||
# UN LIEN QUI N'EST NI UN VLAN NI UN PORT (2026-10-13). Le tunnel vers le relais porte
|
# UN LIEN QUI N'EST NI UN VLAN NI UN PORT (2026-10-13). Le tunnel vers le relais porte
|
||||||
# un sous-reseau de liaison, mais aucune etiquette : il ne traverse aucun commutateur.
|
# un sous-reseau de liaison, mais aucune etiquette : il ne traverse aucun commutateur.
|
||||||
|
|
@ -443,6 +448,18 @@ ROLES_HOTE = ("switch", "hyperviseur", "frontiere", "passerelle_amont", "relais"
|
||||||
TUNNELS = ("wireguard",)
|
TUNNELS = ("wireguard",)
|
||||||
|
|
||||||
|
|
||||||
|
def reseaux_tunnel(underlay: dict | None) -> set[str]:
|
||||||
|
"""Les noms des reseaux qui sont des TUNNELS (`tunnel:`).
|
||||||
|
|
||||||
|
ECARTES DES PATTES DE LA FRONTIERE (2026-10-14). Le relais et le lien entre les sites
|
||||||
|
donnent a la frontiere une adresse de plus — mais rien de ce qu'elle EMET (journaux,
|
||||||
|
metriques, heure) n'en part, et rien de ce qui y arrive ne vient du plan de gestion. Les
|
||||||
|
compter a rendu `172.31.0.2` source admise des journaux d'un locataire, en desaccord
|
||||||
|
avec ses regles : un tunnel ne vit que par les regles que le devis declare pour lui.
|
||||||
|
"""
|
||||||
|
return {str(r.get("nom")) for r in reseaux(underlay) if r.get("tunnel") is not None}
|
||||||
|
|
||||||
|
|
||||||
def role_hote(hote: dict) -> str:
|
def role_hote(hote: dict) -> str:
|
||||||
"""Nature d'un equipement declare. `switch` par defaut (compatibilite).
|
"""Nature d'un equipement declare. `switch` par defaut (compatibilite).
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue