frontiere : appliquee, et la contrainte de nommage d'OPNsense consignee

Devis clos : 0 a creer, 0 a retirer, 65 inchange + 15 routes. Flotte verifiee
apres coup -- DNS interne, Internet et apt sans erreur sur les cinq hotes.

OPNsense refuse un alias de 32 caracteres ou plus. La contrainte n'etait ecrite
nulle part et se manifestait a l'APPLICATION, pas au devis. nom_alias abrege
desormais (SERVEUR_ -> SRV_, comme le pare-feu est-ouest) et REFUSE bruyamment
si le nom deborde encore : un devis qui promet un objet que la cible rejettera
n'est pas un devis. Le role devient serveur_cache_site.

« RIEN N'EST APPLIQUE » signifiait « pas encore recharge », pas « rien ecrit » :
les objets etaient dans la config, le pare-feu en marche les ignorait. J'avais
lu le message a l'envers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-24 17:51:29 -04:00
parent fa7e656844
commit a1997774be
10 changed files with 87 additions and 8 deletions

View file

@ -1,5 +1,42 @@
# CHANGELOG — Set-OPS
## 2026-08-24 — La frontière appliquée, et une contrainte qui n'était écrite nulle part
`a creer : 0 | a retirer : 0 | inchange : 65 + 15 routes` — le devis est clos. La flotte de
patient 0 vérifiée après coup : DNS interne, Internet et `apt` sans erreur sur les cinq.
### OPNsense refuse un nom d'alias de 32 caractères ou plus
Le schéma `SETOPS_<tenant>_<ROLE>` ne laisse que 17 caractères au rôle :
```
SETOPS_PATI29_SERVEUR_ARTEFACTS_SITE 36 refusé
SETOPS_PATI29_SRV_ARTEFACTS_SITE 32 refusé encore, d'un caractère
SETOPS_PATI29_SRV_CACHE_SITE 28 passe
```
La contrainte n'était **écrite nulle part**, et elle s'est manifestée à l'**application**,
pas au devis. Un devis qui promet un objet que la cible rejettera n'est pas un devis :
`nom_alias` abrège désormais (`SERVEUR_` → `SRV_`, comme le pare-feu est-ouest) et **refuse
bruyamment** si le nom déborde encore. Le rôle est renommé `serveur_cache_site`.
### « RIEN N'EST APPLIQUÉ » voulait dire autre chose que ce que j'ai lu
Le premier essai a signalé cinq échecs et affiché *« RIEN N'EST APPLIQUÉ, la config reste
en attente »*. J'ai d'abord compris que le boîtier n'avait pas été touché. Le devis
suivant disait autre chose : 62 objets inchangés au lieu de 49.
Les objets **étaient bien écrits** dans la configuration ; c'est le **rechargement** qui
n'avait pas eu lieu — « en attente » au sens d'OPNsense. Une différence qui compte : entre
les deux, la configuration contenait des objets que le pare-feu en marche ignorait encore.
### Et la garde du boîtier injoignable a servi
Une tentative a échoué sur une poignée TLS expirée — le premier paquet après une pause,
mesuré tout au long de la journée. Le code a **refusé de lire un boîtier injoignable comme
un boîtier vide**, au lieu de proposer de tout recréer. C'est exactement ce pour quoi cette
garde avait été écrite.
## 2026-08-24 — Le chaînage des caches, et la règle qui appartient au SITE
Le runner de SITE est le seul à toucher la configuration système du matériel. Sa

View file

@ -53,7 +53,7 @@ Ce que la reconstruction couvre, par capacité :
| Source d'artefacts | `serveur_artefacts`, `client_artefacts` | cache apt de l'ecosysteme (apt-cacher-ng) : les paquets viennent de chez soi, pas de six serveurs etrangers — **mode hors ligne** pour prouver ce que le cache detient vraiment |
| Runner de site | `serveur_ops_site` | le pouvoir de **materialiser** : creer et detruire des VM sur la fabric. Detient la voute du SITE, chiffree, et n'entre JAMAIS chez un tenant — reserve a l'ecosysteme de l'hebergeur |
| Resolution | `serveur_resolveur`, `client_resolveur` | UN resolveur recursif par tenant (Unbound), qui recurse depuis la racine et delegue la zone souveraine a PowerDNS. Remplace les N demons locaux d'avant le 2026-08-24 |
| Cache du site | `serveur_artefacts_site` | designe LE cache que les ecosystemes voisins prennent comme amont : Debian telecharge une fois pour toute la fabric, et le cache ne voit que des requetes agregees. Reserve a l'ecosysteme de l'hebergeur |
| Cache du site | `serveur_cache_site` | designe LE cache que les ecosystemes voisins prennent comme amont : Debian telecharge une fois pour toute la fabric, et le cache ne voit que des requetes agregees. Reserve a l'ecosysteme de l'hebergeur |
| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte |
*Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail`

View file

@ -19,7 +19,7 @@ flux:
# LE CHAINAGE, cote consommateur. Un seul port, et seulement vers un CACHE — jamais
# d'une VM vers le cache d'un autre tenant.
#
# L'INGRESS CORRESPONDANT N'EST PAS ICI : il appartient a `serveur_artefacts_site`, le
# L'INGRESS CORRESPONDANT N'EST PAS ICI : il appartient a `serveur_cache_site`, le
# cache designe du site. Sans cette separation, chaque cache aurait accepte chaque
# autre — un maillage complet, visible au devis du 2026-08-24.
- sens: egress

View file

@ -1,4 +1,4 @@
# serveur_artefacts_site
# serveur_cache_site
**Le cache d'artefacts du SITE** : celui que les écosystèmes voisins prennent comme amont,
pour que Debian ne soit téléchargé qu'une fois pour toute la fabric.

View file

@ -1,5 +1,16 @@
---
# LE CACHE D'ARTEFACTS DU SITE — celui que les autres écosystèmes prennent comme amont.
# LE CACHE D'ARTEFACTS DU SITE — nomme `cache_site` et non `artefacts_site`.
#
# POURQUOI CE NOM COURT (2026-08-24). OPNsense refuse un nom d'alias de 32 caracteres ou
# plus, et le schema `SETOPS_<tenant>_<ROLE>` ne laisse que 17 caracteres au role :
#
# SETOPS_PATI29_SERVEUR_ARTEFACTS_SITE 36 refuse a l'application
# SETOPS_PATI29_SRV_ARTEFACTS_SITE 32 refuse encore, d'un caractere
# SETOPS_PATI29_SRV_CACHE_SITE 28 passe
#
# La contrainte n'etait ecrite nulle part et s'est manifestee a l'APPLICATION, pas au
# devis. Elle l'est desormais, et le devis REFUSE plutot que de promettre un objet que la
# cible rejettera. — celui que les autres écosystèmes prennent comme amont.
#
# Même patron que `serveur_ops_site` : le rôle n'installe rien de plus, il **attribue une
# responsabilité** et la rend lisible dans le plan. `serveur_artefacts` fait le travail ;
@ -14,4 +25,4 @@
# tenant ordinaire qui le déclarerait s'offrirait à servir ses voisins sans y être invité.
#
# Le cache du site va DIRECTEMENT chez Debian : il est le bout de la chaîne.
serveur_artefacts_site_amont_attendu: ""
serveur_cache_site_amont_attendu: ""

View file

@ -7,7 +7,7 @@
- name: Exiger que le cache du site aille directement à l'amont
ansible.builtin.assert:
that:
- (serveur_artefacts_amont | default('')) == serveur_artefacts_site_amont_attendu
- (serveur_artefacts_amont | default('')) == serveur_cache_site_amont_attendu
fail_msg: >-
Ce cache est déclaré cache du SITE : il doit aller directement chez Debian.
`serveur_artefacts_amont` doit rester vide — sinon les caches se chaînent en boucle.

View file

@ -261,9 +261,40 @@ def cibles_par_role(role: str, inventaire: Path) -> list[str]:
return sorted({ips[h] for h in _hotes_du_groupe(data, role) if h in ips})
# OPNsense refuse un nom d'alias de 32 caracteres ou plus. La contrainte n'etait nulle
# part, et elle s'est manifestee a l'APPLICATION — pas au devis :
#
# SETOPS_PATI29_SERVEUR_ARTEFACTS_SITE (36)
# « The name must start with a letter [...] and be less than 32 characters »
#
# L'outil a bien refuse d'appliquer quoi que ce soit, mais le devis, lui, annoncait un
# objet impossible. Un devis qui promet ce que la cible refusera n'est pas un devis.
LONGUEUR_MAX_ALIAS = 31
# Meme abreviation que le pare-feu est-ouest (`devis_proxmox_fw` : serveur_ -> srv-), pour
# qu'un seul schema de lecture serve aux deux couches.
ABREGES = (("SERVEUR_", "SRV_"), ("CLIENT_", "CLI_"))
def nom_alias(role: str, etiquette: str) -> str:
"""Alias d'hotes, PREFIXE par le tenant : deux tenants ont chacun leur nginx."""
return f"SETOPS_{etiquette}_{role.upper()}"
"""Alias d'hotes, PREFIXE par le tenant : deux tenants ont chacun leur nginx.
Abrege SEULEMENT si le nom complet depasse — un nom court reste lisible tel quel, et
la flotte existante n'est pas renommee pour la commodite d'un seul role.
"""
nom = f"SETOPS_{etiquette}_{role.upper()}"
if len(nom) <= LONGUEUR_MAX_ALIAS:
return nom
court = role.upper()
for long, bref in ABREGES:
court = court.replace(long, bref)
nom = f"SETOPS_{etiquette}_{court}"
if len(nom) > LONGUEUR_MAX_ALIAS:
# Mieux vaut un devis qui refuse qu'un devis qui promet l'impossible.
raise SystemExit(
f"Nom d'alias trop long pour OPNsense ({len(nom)} > {LONGUEUR_MAX_ALIAS}) : {nom}.\n"
f"Raccourcir le nom du role `{role}`.")
return nom
def _instance_active() -> tuple[str, dict] | tuple[None, None]: