adressage : le decalage de +10 est retire, l'index se lit dans l'adresse

supernet_de rendait 10.(10+index).0.0/16. Personne ne savait plus pourquoi : ni le
commentaire de la constante, ni le wiki, ni le commit fondateur 36a882b ne le
justifiaient. Trois endroits consultes, zero raison ecrite.

Ses deux effets constates :
  - il reservait 10.0-10.9 sous la plage tenant. Utile tant que l'underlay vivait
    la — mais D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a
    vide cette reserve de son role la veille ;
  - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
    reseau domestique. Ce risque revient donc aux index bas, et c'est ASSUME.

En echange l'index se lit directement dans l'adresse (17 -> 10.17.x.x) et le
plafond passe de 245 a 255 ecosystemes federes.

  Chezlepro (17)   10.27.0.0/16 -> 10.17.0.0/16
  Technolibre (11) 10.21.0.0/16 -> 10.11.0.0/16
  lab (1)          10.11.0.0/16 -> 10.1.0.0/16

Doc alignee : les trois pages du wiki, multi-instances.md (plafond et exemple
devenus faux arithmetiquement), sdn-evpn.md, le libelle de la GUI, la docstring
d'underlay.py, D-77, et le document de preparation d'un site hebergeur. Les
CONSTATS DE TERRAIN dates sont laisses tels quels : ce sont des mesures.

CE COMMIT NE RENUMEROTE RIEN. Il change ce que le plan DERIVE ; l'inventaire
applique porte toujours 10.27.x.x et les quatorze VM tournent dessus. Appliquer
sans reconstruire rendrait la flotte injoignable — le renumerotage est une
operation a part, a mener a froid.

Au passage, retire un debris : une copie de conflit Nextcloud de
serveur_powerdns/defaults/main.yml, IDENTIQUE a l'original, commitee par accident
dans 1295eea et jamais chargee par Ansible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-12 16:16:08 -04:00
parent b7239149bb
commit 5ace6bdb95
14 changed files with 102 additions and 93 deletions

View file

@ -1,5 +1,57 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-08-12 — Le décalage de `+10` est retiré : l'index se lit dans l'adresse
`supernet_de(index)` rendait `10.(10+index).0.0/16`. **Personne ne savait plus pourquoi**
ni le commentaire de la constante, ni le wiki de l'adressage, ni le commit fondateur
`36a882b` ne le justifiaient. Trois endroits consultés, zéro raison écrite.
Ses deux effets constatés :
- il **réservait `10.0``10.9`** sous la plage tenant. Utile tant que l'underlay vivait
là — mais **D-77 l'a fait entrer dans la bande basse de son propre `/16`**, ce qui a vidé
cette réserve de son rôle la veille ;
- il éloignait le premier tenant de `10.0.0.0/16`, la plage la plus répandue en réseau
domestique. **Ce risque revient donc aux index bas, et c'est assumé** : choisir un index,
c'est choisir sa plage — autant que ce soit lisible.
En échange, **l'index se lit directement dans l'adresse** — index 17 → `10.17.x.x` — et le
plafond passe de 245 à 255 écosystèmes fédérés.
| Instance | Avant | Après |
|---|---|---|
| Chezlepro (17) | `10.27.0.0/16` | `10.17.0.0/16` |
| Technolibre (11) | `10.21.0.0/16` | `10.11.0.0/16` |
| lab (1) | `10.11.0.0/16` | `10.1.0.0/16` |
Documentation alignée partout : les trois pages du wiki, `multi-instances.md` (dont le
plafond et l'exemple, devenus faux arithmétiquement), `sdn-evpn.md`, le libellé de la GUI,
la docstring d'`underlay.py`, D-77, et le document de préparation d'un site hébergeur. Les
**constats de terrain datés** — incidents dans les commentaires, `CHANGELOG`, rapports
d'audit — sont laissés tels quels : ce sont des mesures, pas des formules.
### Ce que ce commit ne fait PAS
Il ne renumérote rien. Il change ce que le plan **dérive** ; l'inventaire appliqué, lui,
porte toujours `10.27.x.x`, et les quatorze VM tournent dessus.
```
14 hote(s) avec ecart — ansible_host, proxmox_passerelle, setops_supernet
```
Appliquer cet inventaire **sans reconstruire la flotte la rendrait injoignable** : Ansible
chercherait des machines à des adresses que personne ne porte. Le renumérotage est une
opération à part — `instancier-appliquer`, puis SDN, puis reconstruction, puis frontière —
à mener à froid.
### Au passage : une preuve qui ne peut pas échouer
**P03 « Diff-vide du plan » ne prouve rien.** `instancier.py comparer` affiche l'écart puis
renvoie **toujours `0`** : la preuve passe quel que soit le nombre d'hôtes divergents. Elle
aurait dû crier ici, sur quatorze. Non corrigé dans ce commit — le corriger ferait échouer
`prouver` jusqu'au renumérotage, ce qui est exact mais bloquerait tout le reste. À traiter
**avec** le renumérotage, pas avant.
## 2026-08-12 — P23 outille D-77 : la bande basse devient une règle, pas une convention ## 2026-08-12 — P23 outille D-77 : la bande basse devient une règle, pas une convention
D-77 disait où l'underlay doit vivre. Rien ne le vérifiait — et une convention qu'on D-77 disait où l'underlay doit vivre. Rien ne le vérifiait — et une convention qu'on

View file

@ -15,7 +15,7 @@
|---|---|---|---|---| |---|---|---|---|---|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  | | P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  |
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee | | P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. | | P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 14 hote(s) avec ecart. |
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | | | P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | | | P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | | P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |

View file

@ -117,7 +117,7 @@ sont les seules vérifiables.
| **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` | — | | **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` | — |
| **D-76** | Le **gabarit doit se FABRIQUER par le dépôt**, pas se façonner à la main — décidée le 2026-08-12, **volontairement différée** après Technolibre | tout dérive d'un plan et se prouve ; le gabarit est la **seule pièce faite à la main** — 853 lignes de procédure manuelle — et il est en amont des quatorze VM. Le 2026-08-09 l'a démontré : l'ancien portait une **clé privée d'hôte SSH** et un `/etc/resolv.conf` figé, recopiés dans chaque clone, et il a fallu le recapturer. La preuve de reconstruction s'arrête donc un cran trop tôt : on reconstruit la flotte, pas ce dont elle est clonée. **Cible** : image cloud Debian officielle → signature vérifiée contre une empreinte épinglée (`verifier_signature.py`, déjà écrit) → import, réglages, conversion. **Coût honnête** : la recette doit être COMPARÉE au gabarit courant avant bascule — un réglage oublié serait hérité par les quatorze VM, et découvert loin de sa cause. **En attendant**, un gabarit se transporte d'un cluster à l'autre par `vzdump` / `qmrestore` | `docs/procedure-template-debian13-proxmox.md`, `scripts/verifier_signature.py` | — *(à construire)* | | **D-76** | Le **gabarit doit se FABRIQUER par le dépôt**, pas se façonner à la main — décidée le 2026-08-12, **volontairement différée** après Technolibre | tout dérive d'un plan et se prouve ; le gabarit est la **seule pièce faite à la main** — 853 lignes de procédure manuelle — et il est en amont des quatorze VM. Le 2026-08-09 l'a démontré : l'ancien portait une **clé privée d'hôte SSH** et un `/etc/resolv.conf` figé, recopiés dans chaque clone, et il a fallu le recapturer. La preuve de reconstruction s'arrête donc un cran trop tôt : on reconstruit la flotte, pas ce dont elle est clonée. **Cible** : image cloud Debian officielle → signature vérifiée contre une empreinte épinglée (`verifier_signature.py`, déjà écrit) → import, réglages, conversion. **Coût honnête** : la recette doit être COMPARÉE au gabarit courant avant bascule — un réglage oublié serait hérité par les quatorze VM, et découvert loin de sa cause. **En attendant**, un gabarit se transporte d'un cluster à l'autre par `vzdump` / `qmrestore` | `docs/procedure-template-debian13-proxmox.md`, `scripts/verifier_signature.py` | — *(à construire)* |
| **D-77** | **L'underlay d'un site dérive du même `index` que son tenant**, dans la **bande basse** `10.(10+index).015.x` ; les réseaux de **stockage** en sortent et sont **identiques partout**, en `192.168.<vlan>.0/24` | tant qu'il n'existait qu'un site, `10.0.x.x` suffisait. Deux sites qui doivent se joindre — reprise mutuelle, sauvegardes croisées, exploitation à distance — **ne peuvent pas** porter les mêmes plages : chaque routeur croit que le réseau est chez lui. Un « numéro de site » a d'abord été proposé : **rejeté**, c'était un SECOND SEED à tenir et à synchroniser, alors que tout dérive déjà d'`index` (D-17). La bande basse est sûre **par la règle, pas par chance** : les zones valent `10.(10+index).(15+categorie).0/24`, donc troisième octet ≥ 16 — les octets 0 à 15 ne sont jamais alloués. Les troisièmes et derniers octets de l'underlay actuel sont ainsi **préservés** : seul le deuxième change, et l'invariant `.1` (D-04) survit intact. Le **stockage** en est sorti parce qu'il est jumbo, non routé, et ne quitte jamais son site : il n'a aucun besoin d'être unique, et l'aligner sur le VLAN (`192.168.20.x` ↔ VLAN 20) fait dire son VLAN à l'adresse. Contrepartie assumée : ces réseaux ne pourront jamais traverser un lien inter-sites — sans conséquence, puisque ce qui voyage entre deux sites est l'**état**, par le dépôt de sauvegarde, pas le stockage bloc | `underlay.yml` (`index`), `scripts/underlay.py`, `docs/preparer-un-site-hebergeur.md` | **P23** | | **D-77** | **L'underlay d'un site dérive du même `index` que son tenant**, dans la **bande basse** `10.<index>.015.x` ; les réseaux de **stockage** en sortent et sont **identiques partout**, en `192.168.<vlan>.0/24` | tant qu'il n'existait qu'un site, `10.0.x.x` suffisait. Deux sites qui doivent se joindre — reprise mutuelle, sauvegardes croisées, exploitation à distance — **ne peuvent pas** porter les mêmes plages : chaque routeur croit que le réseau est chez lui. Un « numéro de site » a d'abord été proposé : **rejeté**, c'était un SECOND SEED à tenir et à synchroniser, alors que tout dérive déjà d'`index` (D-17). La bande basse est sûre **par la règle, pas par chance** : les zones valent `10.<index>.(15+categorie).0/24`, donc troisième octet ≥ 16 — les octets 0 à 15 ne sont jamais alloués. Les troisièmes et derniers octets de l'underlay actuel sont ainsi **préservés** : seul le deuxième change, et l'invariant `.1` (D-04) survit intact. Le **stockage** en est sorti parce qu'il est jumbo, non routé, et ne quitte jamais son site : il n'a aucun besoin d'être unique, et l'aligner sur le VLAN (`192.168.20.x` ↔ VLAN 20) fait dire son VLAN à l'adresse. Contrepartie assumée : ces réseaux ne pourront jamais traverser un lien inter-sites — sans conséquence, puisque ce qui voyage entre deux sites est l'**état**, par le dépôt de sauvegarde, pas le stockage bloc | `underlay.yml` (`index`), `scripts/underlay.py`, `docs/preparer-un-site-hebergeur.md` | **P23** |
> **Ce que D-77 rend possible, et ce qu'elle coûte.** Elle est la condition de la reprise > **Ce que D-77 rend possible, et ce qu'elle coûte.** Elle est la condition de la reprise
> mutuelle : sans plages distinctes, aucun lien entre deux sites ne peut router. Elle est > mutuelle : sans plages distinctes, aucun lien entre deux sites ne peut router. Elle est

View file

@ -152,9 +152,9 @@ Ce que `index` dérive (modèle à 6 zones) :
| Élément | Formule | Exemple (index 13) | | Élément | Formule | Exemple (index 13) |
| --- | --- | --- | | --- | --- | --- |
| Supernet | `10.(10+index).0.0/16` | `10.23.0.0/16` | | Supernet | `10.<index>.0.0/16` | `10.13.0.0/16` |
| Sous-réseau de zone | `10.(10+index).(15+zone).0/24` | `10.23.16.0/24` (Frontière) | | Sous-réseau de zone | `10.<index>.(15+zone).0/24` | `10.13.16.0/24` (Frontière) |
| Passerelle | `…(15+zone).1` | `10.23.16.1` | | Passerelle | `…(15+zone).1` | `10.13.16.1` |
| VLAN sur le trunk | `1000 + index×10 + zone` | `1131`-`1136` | | VLAN sur le trunk | `1000 + index×10 + zone` | `1131`-`1136` |
| VMID (ip-miroir) | `VLAN · octet-hôte · séq` (9 chiffres) | `113102101` | | VMID (ip-miroir) | `VLAN · octet-hôte · séq` (9 chiffres) | `113102101` |
@ -166,8 +166,9 @@ mêmes VLAN/VMID : c'est la collision que `make instances` et **P21** attrapent.
`federe: false` : il est alors **exclu du réseau convergé** (devis, `make instances` le `federe: false` : il est alors **exclu du réseau convergé** (devis, `make instances` le
montre « local »). Il garde son adressage dérivé et reste déployable sur *son* infra. montre « local »). Il garde son adressage dérivé et reste déployable sur *son* infra.
**Plafond théorique : 245 écosystèmes fédérés** (index 1 à 245), borné par l'IPv4 **Plafond théorique : 255 écosystèmes fédérés** (index 1 à 255), borné par l'IPv4
`10.(10+index)` (2ᵉ octet 11→255). Le VLAN (≤ 4094) autorise jusqu'à 308, le VMID bien `10.<index>` (2ᵉ octet 1→255). Le décalage de +10, retiré le 2026-08-12, en confisquait
dix — et surtout, il empêchait de lire l'index directement dans l'adresse. Le VLAN (≤ 4094) autorise jusqu'à 308, le VMID bien
plus — c'est donc l'adressage IP qui plafonne. Au-delà d'une poignée d'instances plus — c'est donc l'adressage IP qui plafonne. Au-delà d'une poignée d'instances
co-localisées, confier l'allocation à un **IPAM** (NetBox) plutôt qu'au moteur — cf. co-localisées, confier l'allocation à un **IPAM** (NetBox) plutôt qu'au moteur — cf.
[`positionnement.md`](positionnement.md). [`positionnement.md`](positionnement.md).

View file

@ -31,14 +31,14 @@ basse** du supernet — celle que la dérivation des tenants n'alloue jamais.
| Rôle | VLAN | Sous-réseau | MTU | Qui y vit | | Rôle | VLAN | Sous-réseau | MTU | Qui y vit |
|---|---|---|---|---| |---|---|---|---|---|
| Gestion | 10 | `10.(10+index).0.0/24` | 1500 | frontière `.1`, commutateurs, OOB/IPMI, admin hyperviseur | | Gestion | 10 | `10.<index>.0.0/24` | 1500 | frontière `.1`, commutateurs, OOB/IPMI, admin hyperviseur |
| Sortie des tenants | 40 | `10.(10+index).4.0/24` | 1500 | frontière `.1`, nœuds de sortie | | Sortie des tenants | 40 | `10.<index>.4.0/24` | 1500 | frontière `.1`, nœuds de sortie |
| Transport VXLAN | 11 | `10.(10+index).5.0/24` | 1500 | hyperviseurs seulement | | Transport VXLAN | 11 | `10.<index>.5.0/24` | 1500 | hyperviseurs seulement |
| Stockage iSCSI | 20 | `192.168.20.0/24` | 9000 | baie ↔ hyperviseurs | | Stockage iSCSI | 20 | `192.168.20.0/24` | 9000 | baie ↔ hyperviseurs |
| Ceph public | 30 | `192.168.30.0/24` | 9000 | clients ↔ MON/OSD | | Ceph public | 30 | `192.168.30.0/24` | 9000 | clients ↔ MON/OSD |
| Ceph cluster | 31 | `192.168.31.0/24` | 9000 | OSD ↔ OSD | | Ceph cluster | 31 | `192.168.31.0/24` | 9000 | OSD ↔ OSD |
Index 17 → gestion en `10.27.0.0/24` ; index 11 → `10.21.0.0/24`. Deux sites ne peuvent Index 17 → gestion en `10.17.0.0/24` ; index 11 → `10.11.0.0/24`. Deux sites ne peuvent
donc **pas** se chevaucher, sans qu'on ait rien de plus à décider. donc **pas** se chevaucher, sans qu'on ait rien de plus à décider.
**Les VLAN ne changent jamais** d'un site à l'autre : ils sont locaux, ils ne traversent **Les VLAN ne changent jamais** d'un site à l'autre : ils sont locaux, ils ne traversent
@ -46,7 +46,7 @@ aucun lien inter-sites. Un seul modèle mental, et l'adresse de stockage porte s
numéro de VLAN — `192.168.20.x` est sur le VLAN 20. numéro de VLAN — `192.168.20.x` est sur le VLAN 20.
> **Pourquoi la bande basse est sûre, et non pas seulement libre aujourd'hui.** Les zones > **Pourquoi la bande basse est sûre, et non pas seulement libre aujourd'hui.** Les zones
> d'un tenant sont dérivées en `10.(10+index).(15+categorie).0/24` : leur troisième octet > d'un tenant sont dérivées en `10.<index>.(15+categorie).0/24` : leur troisième octet
> vaut donc **16 au minimum**, et croît avec le nombre de zones. Les octets `0` à `15` > vaut donc **16 au minimum**, et croît avec le nombre de zones. Les octets `0` à `15`
> ne sont **jamais** alloués — c'est une propriété de la règle, pas une place restée > ne sont **jamais** alloués — c'est une propriété de la règle, pas une place restée
> vide. L'underlay y tient largement. > vide. L'underlay y tient largement.
@ -76,8 +76,8 @@ C'est le seul équipement qui route, et l'unique point de passage entre l'extér
l'intérieur. Set-OPS **pilote ses règles par API** : le boîtier reste à l'hébergeur, rien l'intérieur. Set-OPS **pilote ses règles par API** : le boîtier reste à l'hébergeur, rien
n'y est installé. n'y est installé.
**Interfaces :** le WAN (noter l'IP publique) ; la gestion en `10.(10+index).0.1/24` ; le **Interfaces :** le WAN (noter l'IP publique) ; la gestion en `10.<index>.0.1/24` ; le
transit en `10.(10+index).4.1/24`, par où sortent les machines du tenant. transit en `10.<index>.4.1/24`, par où sortent les machines du tenant.
> **Un point de routage porte le même dernier octet partout** : `.1`, quel que soit > **Un point de routage porte le même dernier octet partout** : `.1`, quel que soit
> l'équipement qui l'assure. Invariant porté par la preuve `P23`. > l'équipement qui l'assure. Invariant porté par la preuve `P23`.

View file

@ -34,8 +34,8 @@ Vérifiée sur les deux tenants fédérés, elle ne demande **aucun changement d
| **zone** (un VRF) | le tenant | `CHEZ17` | | **zone** (un VRF) | le tenant | `CHEZ17` |
| **VNet** | la zone de sécurité | `chez174` | | **VNet** | la zone de sécurité | `chez174` |
| **tag** (VNI) | `vlan_de(index, zone)` | `1174` | | **tag** (VNI) | `vlan_de(index, zone)` | `1174` |
| **subnet** | `sous_reseau_de(index, zone)` | `10.27.19.0/24` | | **subnet** | `sous_reseau_de(index, zone)` | `10.17.19.0/24` |
| **gateway** | `passerelle_de(index, zone)` | `10.27.19.1` | | **gateway** | `passerelle_de(index, zone)` | `10.17.19.1` |
> **Rectification du 2026-08-03.** Ce tableau annonçait `chez17-services-infra`, qui > **Rectification du 2026-08-03.** Ce tableau annonçait `chez17-services-infra`, qui
> aurait été **refusé à l'application** : zones et VNets sont limités à **8 caractères** > aurait été **refusé à l'application** : zones et VNets sont limités à **8 caractères**

View file

@ -1,55 +0,0 @@
---
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',
'') }}

View file

@ -156,7 +156,7 @@ CHEMINS_UNDERLAY = {
# (son domaine, son realm, son modele) et des valeurs de l'HEBERGEUR (son cluster, sa # (son domaine, son realm, son modele) et des valeurs de l'HEBERGEUR (son cluster, sa
# frontiere, sa fabric). Ce sont deux proprietaires, deux depots, deux cycles de vie. # frontiere, sa fabric). Ce sont deux proprietaires, deux depots, deux cycles de vie.
INTRANTS_SCHEMA = [ INTRANTS_SCHEMA = [
("index", "reseau", "constante", "Réseau", "Index d'instance dans la fédération (seed : dérive supernet 10.(10+index), VLAN 1000+index×10+zone, VMID)", "int", "tenant"), ("index", "reseau", "constante", "Réseau", "Index d'instance dans la fédération (seed : dérive supernet 10.<index>, VLAN 1000+index×10+zone, VMID)", "int", "tenant"),
("domaine_interne", "identite", "constante", "Identité", "Domaine DNS interne", "str", "tenant"), ("domaine_interne", "identite", "constante", "Identité", "Domaine DNS interne", "str", "tenant"),
("fuseau_horaire", "identite", "defaut", "Identité", "Fuseau horaire", "str", "tenant"), ("fuseau_horaire", "identite", "defaut", "Identité", "Fuseau horaire", "str", "tenant"),
("organisation", "identite", "defaut", "Identité", "Organisation (annuaire LDAP, certificats)", "str", "tenant"), ("organisation", "identite", "defaut", "Identité", "Organisation (annuaire LDAP, certificats)", "str", "tenant"),

View file

@ -454,28 +454,39 @@ def fonction_seq(nom: str) -> tuple[str, int | None]:
# le devis reseau (scripts/devis_reseau.py). # le devis reseau (scripts/devis_reseau.py).
# #
# Conventions du modele a 6 zones (reference integral) : # Conventions du modele a 6 zones (reference integral) :
# - 2e octet du supernet = OCTET_TENANT + index (index 1 -> 10.11.0.0/16) # - 2e octet du supernet = index TEL QUEL (index 1 -> 10.1.0.0/16)
# - 3e octet de la zone = OCTET_ZONE + categorie (zone 1 -> .16, dans .16.0/20) # - 3e octet de la zone = OCTET_ZONE + categorie (zone 1 -> .16, dans .16.0/20)
# - VLAN sur le trunk convergE = SOCLE_VLAN + index x 10 + zone (4 chiffres, unique) # - VLAN sur le trunk convergE = SOCLE_VLAN + index x 10 + zone (4 chiffres, unique)
# - VMID ip-miroir = VLAN(4) . octet-hote(3) . sequence(2) = 9 chiffres mnemotechniques # - VMID ip-miroir = VLAN(4) . octet-hote(3) . sequence(2) = 9 chiffres mnemotechniques
OCTET_TENANT = 10 #
# LE DECALAGE DE +10 A ETE RETIRE le 2026-08-12. Il produisait `10.(10+index)`, et
# personne ne savait plus pourquoi : ni le commentaire de la constante, ni le wiki, ni
# le commit fondateur (36a882b) ne le justifiaient. Ses deux effets constates :
# - il reservait 10.0-10.9 sous la plage tenant — utile tant que l'underlay vivait
# LA ; D-77 l'a fait entrer dans la bande basse de son propre /16, ce qui a vide
# cette reserve de son role ;
# - il eloignait le premier tenant de 10.0.0.0/16, la plage la plus repandue en
# reseau domestique. Ce risque revient donc aux INDEX BAS, et c'est assume :
# choisir un index, c'est choisir sa plage — autant que ce soit lisible.
# En echange, l'index se lit DIRECTEMENT dans l'adresse (index 17 -> 10.17.x.x) et la
# plage utile passe de 0-245 a 0-255.
OCTET_ZONE = 15 OCTET_ZONE = 15
SOCLE_VLAN = 1000 SOCLE_VLAN = 1000
INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable) INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable)
def supernet_de(index: int) -> str: def supernet_de(index: int) -> str:
"""/16 du tenant : 10.(10+index).0.0/16.""" """/16 du tenant : 10.<index>.0.0/16 — l'index se lit dans l'adresse."""
return f"10.{OCTET_TENANT + int(index)}.0.0/16" return f"10.{int(index)}.0.0/16"
def base3_de(index: int, categorie: int) -> str: def base3_de(index: int, categorie: int) -> str:
"""Trois premiers octets d'une zone : 10.(10+index).(15+categorie).""" """Trois premiers octets d'une zone : 10.<index>.(15+categorie)."""
return f"10.{OCTET_TENANT + int(index)}.{OCTET_ZONE + int(categorie)}" return f"10.{int(index)}.{OCTET_ZONE + int(categorie)}"
def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str: def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str:
"""/24 d'une zone : 10.(10+index).(15+categorie).0/24.""" """/24 d'une zone : 10.<index>.(15+categorie).0/24."""
return f"{base3_de(index, categorie)}.0/{int(cidr)}" return f"{base3_de(index, categorie)}.0/{int(cidr)}"

View file

@ -21,7 +21,7 @@ sys.path.insert(0, str(RACINE / "scripts"))
import underlay as u # noqa: E402 import underlay as u # noqa: E402
from inventory_rules import OCTET_ZONE # noqa: E402 from inventory_rules import OCTET_ZONE # noqa: E402
SUPERNETS = ["10.21.0.0/16", "10.27.0.0/16"] # deux sites : index 11 et 17 SUPERNETS = ["10.11.0.0/16", "10.17.0.0/16"] # deux sites : index 11 et 17
MIEN = 11 MIEN = 11
@ -39,25 +39,25 @@ def test_la_borne_derive_de_la_regle_des_zones():
def test_bande_basse_de_son_propre_supernet_acceptee(): def test_bande_basse_de_son_propre_supernet_acceptee():
for sous in ("10.21.0.0/24", "10.21.4.0/24", "10.21.5.0/24", "10.21.15.0/24"): for sous in ("10.11.0.0/24", "10.11.4.0/24", "10.11.5.0/24", "10.11.15.0/24"):
assert _erreurs(sous) == [], f"{sous} devrait etre conforme (bande basse)" assert _erreurs(sous) == [], f"{sous} devrait etre conforme (bande basse)"
def test_bande_des_zones_refusee(): def test_bande_des_zones_refusee():
for sous in (f"10.21.{OCTET_ZONE + 1}.0/24", "10.21.18.0/24", "10.21.16.0/20"): for sous in (f"10.11.{OCTET_ZONE + 1}.0/24", "10.11.18.0/24", "10.11.16.0/20"):
assert _erreurs(sous), f"{sous} devrait etre REFUSE (bande des zones)" assert _erreurs(sous), f"{sous} devrait etre REFUSE (bande des zones)"
def test_un_prefixe_qui_franchit_la_frontiere_est_refuse(): def test_un_prefixe_qui_franchit_la_frontiere_est_refuse():
"""Le piege : il COMMENCE dans la bande basse et deborde. Un test qui ne """Le piege : il COMMENCE dans la bande basse et deborde. Un test qui ne
regarderait que le premier octet le laisserait passer.""" regarderait que le premier octet le laisserait passer."""
for sous in ("10.21.0.0/19", "10.21.0.0/16"): for sous in ("10.11.0.0/19", "10.11.0.0/16"):
assert _erreurs(sous), f"{sous} couvre au-dela de {OCTET_ZONE} : a refuser" assert _erreurs(sous), f"{sous} couvre au-dela de {OCTET_ZONE} : a refuser"
def test_supernet_d_un_autre_site_refuse(): def test_supernet_d_un_autre_site_refuse():
"""C'est ce qui empeche deux sites d'etre relies — l'erreur la plus couteuse.""" """C'est ce qui empeche deux sites d'etre relies — l'erreur la plus couteuse."""
assert _erreurs("10.27.0.0/24"), "le supernet d'un autre site doit etre refuse" assert _erreurs("10.17.0.0/24"), "le supernet d'un autre site doit etre refuse"
def test_hors_de_tout_supernet_accepte(): def test_hors_de_tout_supernet_accepte():
@ -69,7 +69,7 @@ def test_hors_de_tout_supernet_accepte():
def test_sans_index_on_retombe_sur_la_regle_stricte(): def test_sans_index_on_retombe_sur_la_regle_stricte():
"""Un underlay qui n'a pas encore migre ne declare pas d'index : aucun chevauchement """Un underlay qui n'a pas encore migre ne declare pas d'index : aucun chevauchement
n'est alors tolere, ce qui reste le comportement sur.""" n'est alors tolere, ce qui reste le comportement sur."""
assert _erreurs("10.21.0.0/24", index=None), \ assert _erreurs("10.11.0.0/24", index=None), \
"sans index, tout chevauchement doit etre refuse" "sans index, tout chevauchement doit etre refuse"

View file

@ -8,9 +8,9 @@ flotte, ils n'appartiennent a aucun tenant.
Convention : VLAN < 1000, franchement SOUS la plage tenant (VLAN = 1000+index*10+zone). Convention : VLAN < 1000, franchement SOUS la plage tenant (VLAN = 1000+index*10+zone).
ADRESSAGE (D-77, 2026-08-12). Un site derive du meme `index` que son tenant, et son ADRESSAGE (D-77, 2026-08-12). Un site derive du meme `index` que son tenant, et son
underlay occupe la BANDE BASSE de ce supernet : `10.(10+index).0-15.x`. La bande est underlay occupe la BANDE BASSE de ce supernet : `10.<index>.0-15.x`. La bande est
sure PAR LA REGLE et non par chance les zones d'un tenant valent sure PAR LA REGLE et non par chance les zones d'un tenant valent
`10.(10+index).(15+categorie).0/24`, donc troisieme octet >= 16 ; les octets 0 a 15 `10.<index>.(15+categorie).0/24`, donc troisieme octet >= 16 ; les octets 0 a 15
ne sont jamais alloues. Deux sites ne peuvent alors pas se chevaucher, sans second ne sont jamais alloues. Deux sites ne peuvent alors pas se chevaucher, sans second
registre a tenir. registre a tenir.
@ -57,7 +57,7 @@ from inventory_rules import supernet_de, vlan_de # noqa: E402
SEUIL_VLAN_TENANT = 1000 SEUIL_VLAN_TENANT = 1000
# Premier troisieme-octet alloue aux ZONES d'un tenant. DERIVE de la regle # Premier troisieme-octet alloue aux ZONES d'un tenant. DERIVE de la regle
# (`base3_de` = 10.(10+index).(15+categorie)) et jamais ecrit en dur : deplacer # (`base3_de` = 10.<index>.(15+categorie)) et jamais ecrit en dur : deplacer
# OCTET_ZONE deplacerait la frontiere de la bande basse avec lui. # OCTET_ZONE deplacerait la frontiere de la bande basse avec lui.
from inventory_rules import OCTET_ZONE # noqa: E402 from inventory_rules import OCTET_ZONE # noqa: E402
PREMIER_OCTET_ZONE = OCTET_ZONE + 1 PREMIER_OCTET_ZONE = OCTET_ZONE + 1

View file

@ -27,7 +27,7 @@ serveurs. Refusé par la preuve **P06** et par la création de modèle.
1ʳᵉ fois). Cf. [Infra as Code & idempotence](Infra-as-Code-et-idempotence). 1ʳᵉ fois). Cf. [Infra as Code & idempotence](Infra-as-Code-et-idempotence).
**Index (seed)** — Le **seul** intrant d'adressage d'une instance. Détermine supernet **Index (seed)** — Le **seul** intrant d'adressage d'une instance. Détermine supernet
(`10.(10+index)`), VLAN (`1000+index×10+zone`), VMID. Unique par instance fédérée. (`10.<index>`), VLAN (`1000+index×10+zone`), VMID. Unique par instance fédérée.
**Instance** — Un écosystème réel (un *tenant*) : `../OPS-<nom>`, avec ses valeurs concrètes **Instance** — Un écosystème réel (un *tenant*) : `../OPS-<nom>`, avec ses valeurs concrètes
(domaine, *voûte*, index). L'active est celle que pointe le symlink `instance/`. (domaine, *voûte*, index). L'active est celle que pointe le symlink `instance/`.

View file

@ -37,8 +37,8 @@ Le **plan** (`instance/plan/`) est la source unique. Cinq registres :
| Élément | Formule (modèle 6 zones) | | Élément | Formule (modèle 6 zones) |
|---|---| |---|---|
| Supernet | `10.(10+index).0.0/16` | | Supernet | `10.<index>.0.0/16` |
| Sous-réseau de zone | `10.(10+index).(15+zone).0/24` | | Sous-réseau de zone | `10.<index>.(15+zone).0/24` |
| Passerelle | `…(15+zone).1` | | Passerelle | `…(15+zone).1` |
| VLAN sur le trunk | `1000 + index×10 + zone` | | VLAN sur le trunk | `1000 + index×10 + zone` |
| VMID | `VLAN·octet-hôte·séquence` (9 chiffres) | | VMID | `VLAN·octet-hôte·séquence` (9 chiffres) |

View file

@ -120,7 +120,7 @@ En mode `sdn`, ces réseaux transportent du VXLAN : leur **MTU doit atteindre 15
sinon le ping passe et les transferts échouent. `make underlay` le refuse. sinon le ping passe et les transferts échouent. `make underlay` le refuse.
`make underlay` l'affiche et le **valide** : VLAN < 1000 et sous-réseaux hors des supernets `make underlay` l'affiche et le **valide** : VLAN < 1000 et sous-réseaux hors des supernets
tenant (`10.(10+index).0.0/16`) — **aucune collision possible** avec les overlays. `make tenant (`10.<index>.0.0/16`) — **aucune collision possible** avec les overlays. `make
devis-reseau` en émet la config (section 0) et l'ajoute au trunk. La **preuve P23** garde la devis-reseau` en émet la config (section 0) et l'ajoute au trunk. La **preuve P23** garde la
règle ; elle est *sautée* si aucun `underlay.yml` n'est défini. règle ; elle est *sautée* si aucun `underlay.yml` n'est défini.