From 912a84483f3a1df4cf9e163dc579f054d72ad4f1 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 12 Sep 2026 14:16:58 -0400 Subject: [PATCH] squelette du site de Technolibre, et la liste de ce qu il faut relever Un seul noeud Proxmox en version 9, une frontiere OPNsense. Trois consequences ecrites dans le README : le SPOF est total, les clones sont complets (un clone lie n achete rien sans second noeud), et rien n a jamais tourne contre un PVE 9. Le chiffre a verifier avant de cabler : 57 Go de RAM pour le site et son locataire sur la meme machine. La RAM est la contrainte dure. Adressage au statu quo : gestion en 10.23.0.0/24, zones du site en 10.0.31-36 comme chez Chezlepro. Consequence connue et ecrite : les deux sites ne pourront pas etre relies tant qu aucun n est renumerote. COLLECTE.md liste dans l ordre ce qui doit etre releve sur place, dont la question de forme : ce qu il y a entre le noeud et l OPNsense decide du mode de routage. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q --- .gitignore | 2 + COLLECTE.md | 98 ++++++++++++++++++++ README.md | 67 ++++++++++++++ opnsense.yml | 25 +++++ plan/10-intrants.yml | 36 ++++++++ plan/applications.yml | 173 ++++++++++++++++++++++++++++++++++ plan/bases-donnees.yml | 26 ++++++ plan/serveurs.yml | 205 +++++++++++++++++++++++++++++++++++++++++ underlay.yml | 84 +++++++++++++++++ 9 files changed, 716 insertions(+) create mode 100644 .gitignore create mode 100644 COLLECTE.md create mode 100644 README.md create mode 100644 opnsense.yml create mode 100644 plan/10-intrants.yml create mode 100644 plan/applications.yml create mode 100644 plan/bases-donnees.yml create mode 100644 plan/serveurs.yml create mode 100644 underlay.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2d4b21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.vault.yml.bak +flux-genere/ diff --git a/COLLECTE.md b/COLLECTE.md new file mode 100644 index 0000000..5da1ed9 --- /dev/null +++ b/COLLECTE.md @@ -0,0 +1,98 @@ +# À relever chez Technolibre — avant de lancer quoi que ce soit + +> Une ligne non remplie ici est une ligne qui bloquera le déploiement plus tard, à un +> endroit où le message d'erreur ne dira pas qu'elle manquait. +> Référence : `docs/preparer-un-site-hebergeur.md` §7, adapté au cas **un seul nœud**. + +## 1. Le chiffre qui décide de tout + +``` +SITE 7 VM 20 Go RAM 776 Go provisionnés +tenant 14 VM 37 Go RAM 460 Go provisionnés + ───────────────────────────────────────── +TOTAL 21 VM 57 Go RAM ~1,2 To +``` + +Sur trois nœuds ça se répartit. Sur **un seul**, la même machine porte tout — et la RAM est +la contrainte dure (le disque se dégonfle en provisionnement fin, pas la mémoire). + +- [ ] **RAM physique du nœud** : ______ Go → si < 64, décider de réduire AVANT de câbler +- [ ] **Espace libre du stockage `images`** : ______ Go (viser 1,2 To ; 800 Go à la rigueur) + +## 2. L'hyperviseur + +- [ ] **Nom exact du nœud** tel qu'il apparaît dans l'interface : ______ +- [ ] **Version** : `pveversion | head -1` → ______ (attendu : `pve-manager/9.x`) +- [ ] **Noms exacts des stockages** acceptant le contenu `images` : `pvesm status` +- [ ] **Ponts** présents : `ip -br link | grep vmbr` +- [ ] **Adresse de gestion** du nœud : ______ +- [ ] **SDN disponible ?** `pvesh get /cluster/sdn` répond sans erreur : oui / non + +### Compte d'API +``` +pveum user add ansible@pve +pveum aclmod / -user ansible@pve -role Administrator +pveum user token add ansible@pve set-ops --privsep 0 +``` +- [ ] **Token ID** + **secret** notés (le secret ne s'affiche **qu'une fois**) + +## 3. La frontière (OPNsense) + +- [ ] **IP publique (WAN)** : ______ +- [ ] **Version** : ______ +- [ ] **Interfaces libres** pour les zones du site : ______ +- [ ] **Clé + secret d'API** créés et notés +- [ ] **Compte `ansible`** en SSH par clé, `sudo` vérifié : `sudo -n -l` +- [ ] **Une patte face au nœud Proxmox** (le lien de transit) : interface ______ + +## 4. Entre les deux — LA question de forme + +Ce qu'il y a entre le nœud Proxmox et l'OPNsense décide du mode de routage : + +- [ ] **Un commutateur capable de SVI et d'ACL** → mode `switch`, modèle : ______ +- [ ] **Un commutateur simple (VLAN seulement)** → l'OPNsense route tout, mode `switch` + avec l'OPNsense comme routeur +- [ ] **Un lien direct nœud ↔ OPNsense** → trunk 802.1Q, l'OPNsense route tout +- [ ] **SDN EVPN sur le nœud** → indépendant du matériel, mais jamais éprouvé sur un + nœud unique + +## 5. Cohabitation d'adressage + +- [ ] `ip route` sur le poste de Mathieu — quelles plages `10.x` sont déjà prises ? +- [ ] Son réseau bureautique / son VPN utilisent-ils `10.0.3x` ou `10.23.x` ? ______ +- [ ] Docker présent sur ses machines d'administration ? (`172.17`+ déjà pris) + +## 6. Le gabarit + +- [ ] Gabarit Debian 13 `modeleSetOPS-minimal` : **fait** / **à faire** + (procédure : `docs/procedure-template-debian13-proxmox.md`, ~30 min) +- [ ] Si fait : **VMID** ______ , **nom** ______ , **stockage** ______ +- [ ] Vérifier `machine: q35` et `bios: ovmf` — **ne jamais convertir** un i440fx + +## 7. Les accès réseau nécessaires depuis le poste + +| Cible | Port | Pour quoi | OK | +|---|---|---|---| +| Hyperviseur | 8006 | créer et détruire les VM | [ ] | +| Hyperviseur | 22 | `pvesh`, `qm`, SDN | [ ] | +| Frontière | 443 | poser les règles par API | [ ] | +| Frontière | 22 | SSH, compte `ansible` | [ ] | +| Zones du site | 22 | SSH vers les VM une fois créées | [ ] | + +## 8. Les deux paires de secrets + +Par un canal chiffré, **séparément du reste**, et jamais dans git : + +- [ ] Token ID + secret de l'hyperviseur → voûte de l'instance +- [ ] Clé + secret de l'API de la frontière → voûte de l'instance + +## 9. Ce qui n'a jamais été éprouvé, et qu'il faut regarder de près + +- [ ] **Proxmox 9** — le moteur n'analyse aucune version et n'appelle que des chemins + d'API stables, mais rien n'a jamais tourné contre un PVE 9. Surveiller le pare-feu + (`proxmox-firewall` nftables en 9) et le SDN (passé GA). +- [ ] **Nœud unique** — pas de Ceph, pas de migration, le gabarit et les clones sont sur + la même machine. Le SPOF est total et assumé. +- [ ] **Deuxième site** — `10.0.31–36` est déjà l'adressage du site de Chezlepro. Les + réutiliser ici ferme la possibilité de relier les deux sites (§8 du guide) tant + qu'aucun n'est renuméroté. diff --git a/README.md b/README.md new file mode 100644 index 0000000..80638bb --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# SITE-Technolibre + +Le dépôt de l'**hébergeur** Technolibre : son matériel, ses zones, ses sept machines de +site. Il n'est pas le plan d'un locataire — celui-là vit dans `OPS-Technolibre` (index 23). + +> **État : SQUELETTE.** Tout ce qui est marqué `<<…>>` est à relever sur place. +> La liste complète, dans l'ordre où la poser : **`COLLECTE.md`**. + +## Ce qui le distingue du site de Chezlepro + +| | Chezlepro | Technolibre | +|---|---|---| +| Nœuds Proxmox | 3 (asgard, gandalf, vishnu) | **1** | +| Version | PVE 8.4 | **PVE 9** | +| Stockage partagé | Ceph NVMe + TrueNAS iSCSI | **local au nœud** | +| Clones | liés, sur stockage partagé | **complets** — pas d'autre nœud pour porter le gabarit | +| Frontière | OPNsense | OPNsense | + +Trois conséquences directes : + +1. **Le SPOF est total et assumé.** Le gabarit, ses clones et le site entier sont sur la + même machine. Perdre le nœud, c'est tout perdre — d'où l'importance du dépôt de + sauvegarde hors nœud dès le premier jour. +2. **`clone_complet: true`.** Un clone lié dépend à vie de son gabarit ; sans second nœud, + ce lien n'achète rien et coûte une dépendance. +3. **Proxmox 9 n'a jamais été éprouvé.** Le moteur n'analyse aucune version et n'appelle + que des chemins d'API stables, mais c'est la première fois. Surveiller le pare-feu + (`proxmox-firewall` nftables depuis la 9) et le SDN (passé GA). + +## Le chiffre à vérifier en premier + +``` +SITE 7 VM 20 Go RAM 776 Go provisionnés +tenant 14 VM 37 Go RAM 460 Go provisionnés + ───────────────────────────────────────── +TOTAL 21 VM 57 Go RAM ~1,2 To +``` + +Sur un seul nœud, la même machine porte tout, et **la RAM est la contrainte dure**. En +dessous de 64 Go, il faut décider de réduire avant de câbler, pas pendant le déploiement. + +## L'adressage + +- **Gestion** : `10.23.0.0/24` — la bande basse du supernet de son locataire, que la + dérivation des zones n'alloue jamais (elles commencent à `.16`). +- **Zones du site** : `10.0.31` à `10.0.36` — **identiques à celles de Chezlepro**. + Statu quo décidé le 2026-09-12 : un index se redéploie assez facilement pour qu'une + collision ne justifie pas de revoir la méthode. + > Conséquence connue : les deux sites ne peuvent pas être reliés (§8 du guide de + > préparation) tant qu'aucun des deux n'est renuméroté. +- **Chemins** (transit) : `10.0.4.0/24`, comme chez Chezlepro. + +## L'ordre des gestes, le jour J + +1. Remplir `COLLECTE.md` **en entier** — une ligne vide bloque plus tard, ailleurs. +2. Reporter les valeurs dans `underlay.yml`, `plan/10-intrants.yml`, `opnsense.yml`. +3. Trancher le mode de routage devant le matériel (`COLLECTE.md` §4). +4. Gabarit : vérifier ou fabriquer (`docs/procedure-template-debian13-proxmox.md`). +5. Voûte : y déposer les deux paires de secrets, jamais dans git. +6. `make underlay` — le devis lit le site réel et refuse ce qui ne concorde pas. +7. `make site-creer CONFIRMER=true`, puis le socle. + +## Voir aussi + +- `docs/preparer-un-site-hebergeur.md` — ce qu'un hébergeur doit fournir +- `docs/implanter-un-tenant-sur-un-site.md` — y poser `OPS-Technolibre` ensuite +- `docs/hebergeur-exploitation.md` — l'exploitation courante diff --git a/opnsense.yml b/opnsense.yml new file mode 100644 index 0000000..648f661 --- /dev/null +++ b/opnsense.yml @@ -0,0 +1,25 @@ +# Intrants de la frontière de Technolibre (OPNsense). +# Les SECRETS (clé et secret d'API) ne vivent PAS ici : voûte chiffrée de l'instance. +--- +opnsense_api_url: https://<> +opnsense_wan_ip: <> + +# L'interface d'arrivée de chaque zone. D-61 : le devis raisonne en ARRIVÉE, donc une +# règle posée sur la mauvaise patte ne correspond JAMAIS — et rien ne le signale. +# Relever les noms exacts (`optN`) dans Interfaces > Assignments. +opnsense_if_zones: + site-pilotage: <> + site-autorite: <> + site-genome: <> + site-service: <> + site-sauvegarde: <> + site-supervision: <> + # La patte face au nœud Proxmox — par où arrive TOUT le trafic des locataires. + # Elle manquait chez Chezlepro et les règles entrantes des tenants ne correspondaient + # à rien : la poser dès le premier jour. + transit-frontiere: <> + +opnsense_if_transit: <> +opnsense_if_wan: wan +opnsense_if_gestion: <> +opnsense_if_site: <> diff --git a/plan/10-intrants.yml b/plan/10-intrants.yml new file mode 100644 index 0000000..15e31ec --- /dev/null +++ b/plan/10-intrants.yml @@ -0,0 +1,36 @@ +# Intrants de base du SITE-Technolibre — les valeurs-racines dont tout le reste dérive. +# ⚠ Ce qui est marqué <<…>> est à relever sur place. Voir COLLECTE.md. +--- +# LE DOMAINE INTERNE DU SITE, pas celui de son locataire. Chezlepro utilise +# `genese.internal` ; deux sites qui porteraient le même nom de zone ne pourraient pas +# coexister dans un même résolveur. Proposition : `technolibre.site` ou `atelier.internal`. +domaine_interne: <> +organisation: <> + +# Par où l'administration entre. Chez Chezlepro c'est la patte LAN de la frontière ; +# ici ce sera la patte de gestion de l'OPNsense (10.23.0.1) ou un accès direct. +rebond: ansible@<> +cle_ssh: ~/.ssh/<> + +integrations_exemptes: {} + +# La clé publique du runner du site — GÉNÉRÉE au premier déploiement de `site-ops-01`, +# puis recopiée ici. Laisser vide au départ : c'est normal. +runner_cle_publique: "" + +# LE GABARIT. Sur un nœud unique il n'y a pas d'autre machine pour le porter : +# ne jamais le personnaliser, et ne jamais convertir une VM i440fx en q35. +gabarit: + vmid: <> + nom: modeleSetOPS-minimal + noeud: <> + machine: q35 + bios: ovmf + stockage: <> + +# Clé publique du compte de dépôt des sauvegardes du site lui-même — générée au +# déploiement de `site-backup-01`, recopiée ensuite. +backup_pubkey: "" + +nftables_admin_ssh: [] +supervision_courriel: <> diff --git a/plan/applications.yml b/plan/applications.yml new file mode 100644 index 0000000..4c2f7b5 --- /dev/null +++ b/plan/applications.yml @@ -0,0 +1,173 @@ +--- +# Les services du SITE et leur placement — même forme que chez un tenant. +# +# `groupe` est le rôle Ansible, `hote` la machine de `plan/serveurs.yml` qui le porte. +# C'est de ce registre que l'inventaire tire ses groupes : une application déclarée ici +# range son hôte dans le groupe correspondant, et `playbooks/groupes/.yml` le +# trouve sans qu'on câble quoi que ce soit. +# +# `expose:` NOMME LE SERVICE, indépendamment de la machine qui le rend. +# +# C'est la pièce qui manquait, et son absence s'est vue au pire endroit : le certificat +# de la forge portait `forge.<>` dans ses SAN, mais **aucune zone ne +# résolvait ce nom**. On avait donc du TLS correct sur un nom que personne ne pouvait +# appeler — et l'usage retombait sur `site-forge-01`, c'est-à-dire sur la machine. +# +# Un nom de service survit au déménagement du service ; un nom de machine, non. + +applications: + # LE RUNNER DU SITE — DEUX RÔLES, ET DANS CET ORDRE. + # + # `serveur_ops` INSTALLE : ansible, le cache de collections, les dépôts du génome clonés + # depuis la forge du site, les symlinks du moteur. `serveur_ops_site` n'installe rien — + # il dépose la voûte de l'underlay (chiffrée) et ajoute le pouvoir de MATÉRIALISER. + # + # C'est le même patron que `serveur_artefacts` + `serveur_cache_site` : un installateur + # et un marqueur. J'avais déclaré le marqueur seul, et son assertion l'a dit sans + # détour : « exige `serveur_ops_site_depot`, cloné par `serveur_ops` ». + ops: + groupe: serveur_ops + hote: site-ops-01 + ops_site: + groupe: serveur_ops_site + hote: site-ops-01 + + # LA SOURCE D'ARTEFACTS, et son marquage comme racine de chaîne. Les deux vont + # ensemble : `serveur_artefacts` INSTALLE apt-cacher-ng, `serveur_cache_site` MARQUE ce + # cache comme racine et vérifie qu'il n'a pas d'amont — il n'installe rien. + artefacts: + groupe: serveur_artefacts + hote: site-cache-01 + cache_site: + groupe: serveur_cache_site + hote: site-cache-01 + + # LA FORGE DU GÉNOME. `expose` porte son nom de service : c'est lui que le certificat + # doit couvrir, et lui que la zone souveraine doit résoudre. + forgejo: + groupe: serveur_forgejo + hote: site-forge-01 + # 443, ET NON 3000. Le `3000` est le port que Forgejo ecoute DERRIERE un edge, en + # clair. Ici il sert lui-meme son TLS : garder 3000 aurait grave `:3000` dans + # `ROOT_URL`, donc dans l'`origin` de chaque ecosysteme descendant, pour toujours. + port: 443 + expose: + - forge.<> + # LE MARQUEUR DE LA FORGE DU GÉNOME — même patron que `artefacts` + `cache_site`. + # + # Le site rend deux services à ses locataires pendant leur jeunesse : les PAQUETS et le + # GÉNOME. Le premier était déclaré, le second ne l'était pas — alors que D-81 fait de + # cette forge l'autorité dont tout écosystème se reproduit. + # + # Mesuré le 2026-08-28 sur `ops-01`, première machine de la reconstruction de + # Chezlepro : le cache du site répondait, la forge non, et le clonage du génome + # attendait sans fin. Le tenant déclarait pourtant lire son génome à cette adresse. + forge_site: + groupe: serveur_forge_site + hote: site-forge-01 + + # L'AUTORITÉ. Elle sert le 443 à ses voisins du même segment, en L2 — rien ne traverse + # la frontière, donc aucune règle ne l'accompagne au devis. `expose` lui donne quand + # même son nom de service : les clients s'y enrôlent par un nom, jamais par une adresse. + step_ca: + groupe: serveur_step_ca + hote: site-pki-01 + expose: + - pki.<> + + # LE DNS — autoritatif et résolveur sur la même machine, à dessein. + powerdns: + groupe: serveur_powerdns + hote: site-dns-01 + resolveur: + groupe: serveur_resolveur + hote: site-dns-01 + expose: + - dns.<> + # LE MARQUEUR DU RÉSOLVEUR DU SITE — troisième service prêté aux locataires, après les + # paquets (`cache_site`) et le génome (`forge_site`). Même patron : un installateur, un + # marqueur. + # + # Un écosystème neuf n'a ni cache, ni forge, ni résolveur — et c'est lui qui doit les + # construire. Sans celui-ci, ses machines sortent en 443 par adresse et restent + # AVEUGLES AUX NOMS : `apt` s'en sort par le mandataire du cache, qui résout à sa place, + # mais un `get_url` ou un dépôt tiers en HTTPS échoue. Mesuré le 2026-08-30 sur + # `infra-pki-01`, première machine à porter l'autorité de Chezlepro. + # + # `client_resolveur` bascule chaque tenant chez lui dès que son propre résolveur existe. + # Retirer cet emprunt ce jour-là est une émancipation — un geste, pas un effet. + resolveur_site: + groupe: serveur_resolveur_site + hote: site-dns-01 + # LE SERVEUR DE SAUVEGARDE, ET SON MARQUEUR — meme patron que les trois autres + # services pretes : `serveur_backup` INSTALLE le depot, `serveur_backup_site` dit qu'il + # sert les locataires et declare le flux qui le permet. + backup: + groupe: serveur_backup + hote: site-backup-01 + backup_site: + groupe: serveur_backup_site + hote: site-backup-01 + # LE NOM QUE LES LOCATAIRES ÉCRIVENT DANS LEUR CONFIGURATION. + # + # Un locataire grave l'adresse de son dépôt dans `client_backup_repo`, donc dans le + # chemin de CHACUN de ses instantanés. Y graver `10.0.35.11` rendrait le dépôt + # indéplaçable : le jour où il change de machine, tous les locataires cessent de + # déposer, et chacun doit être modifié un par un — pendant que plus rien n'est + # sauvegardé. + # + # `sauvegarde`, pas `site-backup-01` : c'est le SERVICE qu'on nomme. La machine qui + # le rend a le droit de changer sans que personne ne réécrive quoi que ce soit. + expose: + - sauvegarde.<> + + # LA SUPERVISION DU SITE. `serveur_backup` rapporte PASSIVEMENT ici, avec un `ttl` : + # c'est l'expiration de ce `ttl` qui fait qu'un SILENCE alerte. Une sauvegarde qui + # cesse ne produit aucune erreur, seulement une absence — sans destinataire pour ce + # rapport, il n'y a pas de supervision, il y a un script qui tourne. + postgresql: + groupe: serveur_postgresql + hote: site-mon-01 + icinga: + groupe: serveur_icinga + hote: site-mon-01 + # L'OBSERVABILITE DU SITE — LA SIENNE, ET RIEN QUE LA SIENNE (D-87, 2026-09-10). + # + # Elle manquait, et son absence etait le REVERS de la frontiere : en refusant de voir + # les journaux de ses locataires — parce qu'ils contiennent du CONTENU, et qu'un + # hebergeur qui les lit en sait plus que s'il detenait leur annuaire — le site s'etait + # prive des siens. Ses sept machines n'expediaient rien nulle part. + # + # Le remede n'est pas d'assouplir la regle : c'est de lui donner SA pile. Aucun lien + # avec celle d'un tenant, dans aucun sens. Un locataire n'y envoie rien, et le site n'y + # lit que ses propres machines. + # + # POSEE SUR `site-mon-01`, la zone de supervision : c'est deja la qu'Icinga juge l'etat + # du site, et le zonage veut qu'une preoccupation vive dans une seule zone. La machine a + # la place (3,3 Go disponibles, charge 0,10 au moment du choix). + prometheus: + groupe: serveur_prometheus + hote: site-mon-01 + loki: + groupe: serveur_loki + hote: site-mon-01 + # GRAFANA SANS SSO, ET C'EST VOULU. Chez un tenant il vit derriere `oauth2_proxy`, donc + # derriere Keycloak. Le site n'a pas d'annuaire — et ne doit pas en avoir un pour ca. + # Faire monter l'identite pour offrir un tableau de bord serait payer tres cher une + # commodite : l'acces se fait depuis le plan d'administration, qui est deja la barriere. + # + # LE NOM DIT LA FONCTION, PAS LE PRODUIT (2026-09-10). C'etait `tableaux.<>`, + # un mot que rien d'autre n'employait. `observatoire` est le meme nom que chez le locataire + # (`observatoire.chezlepro.internal`) : un seul vocabulaire des deux cotes, et un nom qui + # ne ment pas le jour ou Grafana est remplace. + grafana: + groupe: serveur_grafana + hote: site-mon-01 + expose: + - observatoire.<> + + # LE RELAIS DE COURRIEL DU SITE — souverain, et c'est tout l'interet : emprunter le MTA + # d'un locataire ferait dependre le site d'un ecosysteme qu'il peut outvivre. + postfix: + groupe: serveur_postfix + hote: site-mon-01 diff --git a/plan/bases-donnees.yml b/plan/bases-donnees.yml new file mode 100644 index 0000000..a43fcfc --- /dev/null +++ b/plan/bases-donnees.yml @@ -0,0 +1,26 @@ +--- +# LE REGISTRE DES BASES DU SITE — la garde D-72 : sans entree ici, `resoudre_base` +# REFUSE plutot que de deviner un hote, un nom de base ou un secret. +# +# Le site n'en a qu'une, et c'est voulu. Chez un locataire, PostgreSQL vit sur une +# machine dediee parce que plusieurs services la partagent ; ici un seul en a besoin. +# La base est donc sur la machine qui la consomme — une seconde VM n'aurait fait +# qu'ajouter une machine a surveiller a celle qui surveille. +serveurs_bd: + pg-site: + type: postgres + hote: site-mon-01 + port: 5432 + groupe: serveur_postgresql + +bases_donnees: + # LA BASE D'ICINGA. Elle porte l'historique de la supervision — pas irremplacable, + # mais une base l'est par nature, et `site-mon-01` porte donc `client_backup`. + icingadb: + serveur: pg-site + base: icingadb + proprietaire: icingadb + secret: vault_bd_icingadb + consommateur: icinga + portee: application + usage: principale diff --git a/plan/serveurs.yml b/plan/serveurs.yml new file mode 100644 index 0000000..73adb8a --- /dev/null +++ b/plan/serveurs.yml @@ -0,0 +1,205 @@ +--- +# Les machines du SITE — l'équivalent de `plan/serveurs.yml` chez un tenant. +# +# UNE SEULE DIFFÉRENCE AVEC CELUI D'UN TENANT, mais elle est structurante : ici +# l'adressage est **déclaré**, là-bas il est **dérivé**. Un tenant écrit `fonction:` et +# tout descend de son `index` ; un site écrit `ip`, `vmid`, `noeud`, parce qu'il n'y a +# aucun seed dont les faire descendre — le site *est* le terrain. +# +# `reseau:` nomme un réseau de `underlay.yml`, qui fournit le pont, l'étiquette VLAN, le +# masque et la passerelle. La fabric reste la source de ces valeurs-là : on la nomme, on +# ne la recopie pas. +# +# UNE ZONE PAR NATURE D'AUTORITÉ (2026-08-25). Ces machines ont vécu quelques heures dans +# un `/24` plat — la plus autoritaire du système avec moins d'isolation que n'importe +# quelle machine de tenant. Chacune vit désormais dans la zone de ce qu'elle détient, et +# tout ce qui va d'une zone à l'autre est routé, donc policé par la frontière. + +serveurs: + # LE RUNNER DU SITE. Il MATÉRIALISE : VNets, VM, routes, flux — il prépare le terrain + # qu'un runner de tenant occupera ensuite. Il détient la voûte de l'underlay, jamais + # celle d'un tenant. Voir roles/serveur_ops_site/README.md pour la ligne de partage. + site-ops-01: + etat: actif + reseau: site-pilotage + ip: 10.0.31.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 4096, disque: 40G } + vmid: 9001 + variables: + # CE QUE LE RUNNER DU SITE DOIT DÉTENIR POUR TRAVAILLER. + # + # Le moteur, la carte de l'hébergeur, les modèles — et les PLANS qu'il matérialise. + # `tenant` et non `instance` : il ne PILOTE pas ces écosystèmes, il leur prépare le + # terrain. Configurer leurs services exige leurs voûtes, qu'il n'aura jamais. + serveur_ops_depots: + - { depot: "set-ops-public", dest: "Set-OPS-public", role: "moteur" } + - { depot: "site-technolibre", dest: "SITE-Technolibre", role: "hebergeur" } + - { depot: "set-ops-modeles", dest: "Set-OPS-Modeles", role: "modeles", branche: "master" } + - { depot: "ops-technolibre", dest: "OPS-Technolibre", role: "tenant", branche: "master" } + # AUCUNE INSTANCE ACTIVE : un SITE n'est pas un tenant, il n'a pas de plan monté par + # le symlink `instance`. Son propre inventaire est dynamique (`site_inventaire.py`). + serveur_ops_instance: "" + serveur_ops_underlay: "SITE-Technolibre" + # Le dépôt que `serveur_ops_site` habille de la voûte, une fois cloné ci-dessus. + serveur_ops_site_depot: "SITE-Technolibre" + serveur_ops_site_voute_source: >- + {{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/underlay.vault.yml + # ARMER CE RUNNER — il est maître de sa voûte et en détient la clé (2026-08-28). + # + # Sans elle, il porte le chiffré et ne peut rien en faire : il calcule son + # inventaire, et chaque geste qui demande un secret échoue sur une valeur vide. + # + # LA CLÉ DE CE SITE, ET ELLE SEULE. Depuis la séparation des voûtes, le mot de passe + # de l'underlay n'ouvre plus aucun tenant : ce runner ne peut donc ouvrir que la + # fabric, ce qui est exactement son pouvoir. C'était faux jusqu'au 2026-08-28, où + # un mot de passe unique ouvrait les six voûtes de la flotte. + # + # Le chemin est un POINTEUR — le fichier vit sur le poste de l'exploitant et + # n'entre dans aucun dépôt. + serveur_ops_site_cle_deposer: true + serveur_ops_site_cle_source: "~/.config/setops-vault-site-technolibre" + + # LA RACINE DE LA CHAÎNE DE CACHES : + # VM du tenant -> cache du tenant -> cache du SITE -> Debian + # Debian n'est ainsi téléchargé qu'une fois pour tout le site, et ce cache ne voit que + # des requêtes agrégées — jamais quelle machine de quel tenant installe quoi. + site-cache-01: + etat: actif + reseau: site-genome + ip: 10.0.33.21 + noeud: <> + gabarit: { vcpu: 2, memoire: 2048, disque: 120G } + vmid: 9002 + + # LA FORGE DU GÉNOME. Elle vivait chez patient 0 — c'est-à-dire chez un écosystème — + # et tout l'univers en dépendait pour se reproduire. + site-forge-01: + etat: actif + reseau: site-genome + ip: 10.0.33.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 4096, disque: 80G } + vmid: 9003 + # CETTE MACHINE DÉTIENT DE L'ÉTAT — elle dépose donc sur le dépôt du site. + # + # `client_backup` est une intégration FACULTATIVE : elle se déclare là où il y a + # quelque chose à perdre, pas partout. Ici, le génome — les quatre dépôts dont tout descend. Ils vivent + # aussi sur les postes et les runners, mais la forge est le seul endroit où ils se + # rejoignent. + integrations: [client_backup] + variables: + # Elle tourne seule : ni PostgreSQL ni Keycloak sur le site. C'est délibéré — + # cette forge sert LE GÉNOME, pas une équipe. SQLite est un choix de conception, + # pas un repli : la base tient dans un fichier, que la sauvegarde emporte déjà. + serveur_forgejo_bd: sqlite + # Elle sert son propre TLS : il n'y a pas d'edge devant elle sur le site. + serveur_forgejo_tls: true + # Le port du schema, pas celui d'un service derriere un edge. Forgejo tournant en + # `git`, l'unite lui accorde CAP_NET_BIND_SERVICE pour s'y lier. + serveur_forgejo_http_port: 443 + # Forgejo tourne en `git` dès le départ, contrairement à nginx ou Postfix qui + # lisent la clé en root avant de déprivilégier. Accès GROUPE à la clé privée — + # pas un élargissement général. + client_pki_cle_groupe: git + client_pki_cle_mode: "0640" + # Un cert renouvelé sur disque reste servi périmé tant que le consommateur n'est + # pas rechargé. La cicatrice est déjà dans le dépôt ; on ne la refait pas. + client_pki_reload_services: + - forgejo + + # L'AUTORITÉ DE CERTIFICATION DU SITE. Sans elle, la forge servait le génome en clair + # et aucune machine n'avait de certificat — donc aucun chiffrement est-ouest. + site-pki-01: + etat: actif + reseau: site-autorite + ip: 10.0.32.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 2048, disque: 32G } + vmid: 9004 + # CETTE MACHINE DÉTIENT DE L'ÉTAT — elle dépose donc sur le dépôt du site. + # + # `client_backup` est une intégration FACULTATIVE : elle se déclare là où il y a + # quelque chose à perdre, pas partout. Ici, la racine de l'autorité de certification. Compromise, elle forge + # tout nom ; perdue, il faut redistribuer la confiance à chaque machine de chaque + # écosystème. + integrations: [client_backup] + + # LE DNS DU SITE — autoritatif ET résolveur, colocalisés. + # + # Ils partagent la zone souveraine, et le repli de port de PowerDNS (53 -> 5300) DÉRIVE + # de cette colocalisation : le résolveur prend le 53, l'autoritatif se range derrière. + # Les séparer casserait cette dérivation. + site-dns-01: + etat: actif + reseau: site-service + ip: 10.0.34.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 2048, disque: 40G } + vmid: 9005 + variables: + # CORRIGÉ LE 2026-08-25 — c'était `false`, avec pour motif « le site n'a pas d'edge, + # aucun FQDN public à faire pointer quelque part ». Le raisonnement confondait + # PUBLIC et EXPOSÉ. + # + # Le site n'a effectivement aucun domaine public. Mais `plan/applications.yml` + # déclare bien trois expositions — `forge.genese.internal`, `pki.genese.internal`, + # `dns.genese.internal` — et ce sont des noms de SERVICE, que les certificats + # portent et que les clients appellent. Ils n'avaient aucun enregistrement dans la + # zone : seul le plancher `/etc/hosts` savait les résoudre. + # + # Un service ne doit pas dépendre d'un plancher pour être joignable. Le plancher est + # un filet — il rattrape le DNS éteint — pas le sol. + serveur_powerdns_publier_expositions: true + # LE DEPOT DE SAUVEGARDE DU SITE — le quatrieme service prete aux locataires. + # + # Apres les paquets, le genome et les noms : l'ETAT. Un ecosysteme neuf n'a pas de + # depot ou sauvegarder, et c'est justement quand il commence a detenir quelque chose + # d'irremplacable qu'il en a besoin. + # + # CE QUE CA CORRIGE, ET QUI ETAIT DANGEREUX (mesure le 2026-09-01). Chezlepro + # sauvegardait sur `backup-01`, UNE VM DE SA PROPRE FLOTTE. Raser l'ecosysteme aurait + # detruit les donnees ET leur seul filet dans le meme geste. La doctrine affirmait + # pourtant que `client_backup_cible` « pointe deja hors de l'ecosysteme » — vrai pour + # patient 0, qui sauvegarde chez eregion ; faux pour Chezlepro. Un document qui decrit + # une propriete que le reel n'a pas est exactement ce que ce depot traque. + # + # SA ZONE EST A LUI SEUL. Il detient l'etat de CHAQUE tenant : cles d'autorite, + # annuaires, bases, courriel. C'est la machine dont la compromission coute le plus cher + # du site — plus que la forge, dont le contenu est du code, et plus que l'AC, qui forge + # des noms sans detenir de donnees. + # + # SON DISQUE EST LE PLUS GROS DU SITE : il accueille l'etat de tous les locataires, et + # restic garde des instantanes. On le dimensionne large plutot que de decouvrir la + # limite le jour ou une sauvegarde echoue en silence. + site-backup-01: + etat: actif + reseau: site-sauvegarde + ip: 10.0.35.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 2048, disque: 400G } + vmid: 9010 + + # LE TEMOIN DU SITE — celui qui sait quand les autres se taisent. + # + # Il porte SA propre base : chez un locataire, PostgreSQL vit sur une machine dediee + # parce que plusieurs services la partagent. Ici un seul en a besoin, et une seconde VM + # ne ferait qu'ajouter une machine a surveiller a celle qui surveille. + # + # Il porte aussi un relais de courriel. Sans lui, la seule facon d'alerter serait + # d'emprunter le MTA d'un locataire — le site dependrait alors d'un ecosysteme qu'il + # peut outvivre, et une emancipation emporterait son alerte avec elle. + site-mon-01: + etat: actif + reseau: site-supervision + ip: 10.0.36.11 + noeud: <> + gabarit: { vcpu: 2, memoire: 4096, disque: 64G } + vmid: 9011 + # ELLE PORTE UNE BASE, DONC DE L'ETAT — P36 l'a exige des la declaration. + # + # L'historique d'Icinga n'est pas irremplacable ; la base l'est par nature, et le + # catalogue ne connait pas la nuance. C'est tant mieux : une exception ici serait + # exactement ce que cette preuve existe pour empecher. Le dump coute quelques + # megaoctets et vit deja sur le depot du site. + integrations: [client_backup] diff --git a/underlay.yml b/underlay.yml new file mode 100644 index 0000000..b8f26c6 --- /dev/null +++ b/underlay.yml @@ -0,0 +1,84 @@ +# Underlay de SITE-Technolibre — l'infrastructure PHYSIQUE de l'hébergeur. +# +# UN SEUL NŒUD PROXMOX, en version 9. C'est la différence de fond avec le site de +# Chezlepro : pas de Ceph, pas d'iSCSI, pas de migration, pas de fabric de stockage. +# Le gabarit et ses clones vivent sur la même machine — le SPOF est total, et assumé. +# +# Dérivé du modèle `exemples/modeles/socle` (« un seul commutateur, pas de fabric de +# stockage séparée — le point de départ honnête d'un petit hébergeur »), adapté. +# +# ⚠ TOUT CE QUI EST MARQUÉ <<…>> EST À RELEVER SUR PLACE. Voir COLLECTE.md. +--- +underlay: + # Le locataire que ce site hébergera. Son index est déjà fédéré (23) et il ne + # change pas : c'est lui qui porte l'adressage d'OPS-Technolibre, ses VLAN et ses VMID. + tenants: + OPS-Technolibre: 23 + + # CE QUI ROUTE ENTRE LES ZONES D'UN LOCATAIRE — à trancher devant le matériel. + # `switch` : un commutateur porte les SVI et les ACL (mode par défaut du moteur) + # `sdn` : EVPN sur le nœud, un VRF par locataire (éprouvé à 3 nœuds, jamais à 1) + # Voir COLLECTE.md §4 : la réponse dépend de ce qu'il y a entre le nœud et l'OPNsense. + routage_tenants: <> + routeur: <> # le commutateur, ou la frontière si elle route tout + dialecte: <> # dicte la forme des ACL et des routes du devis + mtu_overlay: 1450 + # À `false` quand le matériel ne sait pas lier une ACL à une interface de routage. + acl_inter_tenant: <> + + stp: + mode: rstp + topologie: etoile + + reseaux: + # PLAN DE GESTION — la seule chose qui doit être unique entre deux sites. + # `10..0.0/24`, index 23 : la bande basse du supernet du locataire, que la + # dérivation des zones n'alloue jamais (elles commencent à .16). + - nom: management + description: Gestion du nœud, OOB/IPMI, poste de l'exploitant + vlan: null + sous_reseau: 10.23.0.0/24 + mtu: 1500 + + # LIEN VERS LA FRONTIÈRE. Sans lui, la flotte n'a ni sortie ni chemin de retour : + # elle serait routée jusqu'à la bordure puis muette — très difficile à diagnostiquer. + - nom: transit-frontiere + description: Lien nœud Proxmox <-> OPNsense, et sortie par défaut des locataires + vlan: 40 + sous_reseau: 10.0.4.0/24 + passerelle_sortie: 10.0.4.1 + mtu: 1500 + + # LES SIX ZONES DU SITE. Une préoccupation par zone. + # ⚠ Identiques à celles du site de Chezlepro (statu quo décidé le 2026-09-12). + # Conséquence connue : les deux sites ne pourront pas être reliés (§8 du guide) + # tant qu'aucun des deux n'est renuméroté. + - { nom: site-pilotage, description: Runner et pilotage, vlan: 31, sous_reseau: 10.0.31.0/24, pont: <>, mtu: 1500 } + - { nom: site-autorite, description: Autorité de certification, vlan: 32, sous_reseau: 10.0.32.0/24, pont: <>, mtu: 1500 } + - { nom: site-genome, description: Forge et cache de paquets, vlan: 33, sous_reseau: 10.0.33.0/24, pont: <>, mtu: 1500 } + - { nom: site-service, description: Noms (DNS autoritaire), vlan: 34, sous_reseau: 10.0.34.0/24, pont: <>, mtu: 1500 } + - { nom: site-sauvegarde, description: Dépôt de sauvegarde mutualisé, vlan: 35, sous_reseau: 10.0.35.0/24, pont: <>, mtu: 1500 } + - { nom: site-supervision, description: Supervision et observabilité, vlan: 36, sous_reseau: 10.0.36.0/24, pont: <>, mtu: 1500 } + + hotes: + # L'UNIQUE NŒUD. `via` = l'interface physique qui porte le trunk vers la frontière. + - { nom: <>, reseau: management, ip: <>, role: hyperviseur } + - { nom: <>, reseau: transit-frontiere, ip: 10.0.4.41, role: hyperviseur, via: <> } + + # LA FRONTIÈRE, patte par patte. Une ligne par zone qu'elle sert : c'est de là que + # le devis dérive les interfaces d'arrivée des règles (D-61 raisonne en ARRIVÉE). + - { nom: <>, reseau: management, ip: 10.23.0.1, role: frontiere } + - { nom: <>, reseau: transit-frontiere, ip: 10.0.4.1, role: frontiere } + - { nom: <>, reseau: site-pilotage, ip: 10.0.31.1, role: frontiere } + - { nom: <>, reseau: site-autorite, ip: 10.0.32.1, role: frontiere } + - { nom: <>, reseau: site-genome, ip: 10.0.33.1, role: frontiere } + - { nom: <>, reseau: site-service, ip: 10.0.34.1, role: frontiere } + - { nom: <>, reseau: site-sauvegarde, ip: 10.0.35.1, role: frontiere } + - { nom: <>, reseau: site-supervision, ip: 10.0.36.1, role: frontiere } + + # LE GABARIT ET SON STOCKAGE. Sur un nœud unique, `clone_complet` est le choix sûr : + # un clone lié dépend à vie du gabarit, et il n'y a pas d'autre nœud pour le porter. + materialisation: + vmid_modele: <> + stockage: <> + clone_complet: true