catalogue : la carte des services avait quatre mois de retard sur le moteur
`catalogue-services.md` est le document qu'on lit pour savoir ce que Set-OPS FAIT : l'hebergeur d'un second site, un futur client, un mainteneur qui arrive. Verifie role par role contre roles/, voici ce qu'il disait de faux. - « Capacites futures encore a implementer : collaboration (Nextcloud/Collabora) et couche web (frontal/dorsal) » — les quatre roles existent, collab-01, web-frontal-01 et web-dorsal-01 sont ACTIFS, et les deux roles web sont codifies depuis les spikes du 2026-07-05. - « La federation LDAP n'est pas automatisee dans le role ; Keycloak n'est pas expose » — serveur_keycloak/tasks/federation-ldap.yml existe, et le plan declare `expose: auth.<domaine>`. - `infra-mail-01` : « Sendmail MTA » — c'est Dovecot ; Sendmail est retire depuis le 2026-07-04. La table des hotes datait d'avant la separation edge-mta / mailstore. - `client_supervision` annonce comme integration — n'a JAMAIS eu ni role ni playbook. La supervision ne pose rien sur les hotes : controles actifs depuis le coeur, resultats passifs pousses par l'API (c'est backup-01 qui rapporte l'etat de ses depots). - Une colonne « Role » decorative inventait des noms (`nextcloud`, `client_metriques`) : le role porte le nom du GROUPE. Colonne retiree. Et NEUF roles vivants ne figuraient dans aucune table — le socle, toute la pile courriel, les sauvegardes, Icinga Web 2, oauth2-proxy, Unbound. Deux (`serveur_backup`, `client_backup`) n'etaient nommes NULLE PART. P38 — CE QUE P31 NE POUVAIT PAS VOIR. P31 verifie que tout est nomme et atteignable, pas qu'un document dise vrai : une carte peut etre complete et perimee. P38 confronte le catalogue au code dans les deux sens, et c'est la TABLE qui fait foi des deux cotes : tout role figure dans une ligne de table (la prose ne suffit pas — la pile courriel y etait racontee et introuvable pour qui lit un index), et tout groupe cite en table existe reellement (role, ou playbook de groupe pour `serveur_durci`, qui en compose onze). Deux exemptions nommees : la prose peut citer les roles RETIRES, sinon on ne peut plus ecrire d'ou l'on vient ; et P38 ne juge pas si une description est JUSTE — cela se revoit contre le CHANGELOG, le mecaniser serait se mentir. EPROUVEE EN NEGATIF : rejouee contre la version d'avant, elle echoue en nommant les neuf roles absents et les trois cases fantomes. Le catalogue dit aussi desormais ou il s'arrete : la reconstruction prouve qu'une machine nue atteint l'etat voulu, pas la tenue sous charge ; et l'usage reel de Nextcloud n'est pas consigne comme preuve. Lacune nommee au passage : la dependance causale serveur_web_frontal -> serveur_nginx n'est toujours pas declaree. make prouver : 38 OK, 0 echec, 0 saute. make test inchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
98ab74d047
commit
f01f06df5e
4 changed files with 331 additions and 93 deletions
58
CHANGELOG.md
58
CHANGELOG.md
|
|
@ -1,5 +1,63 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-19 — La carte des services avait quatre mois de retard sur le moteur
|
||||
|
||||
`catalogue-services.md` est le document qu'on lit pour savoir **ce que Set-OPS fait** :
|
||||
l'hébergeur d'un second site, un futur client, un mainteneur qui arrive. Il annonçait
|
||||
comme *« capacités futures encore à implémenter »* la collaboration et la couche web —
|
||||
dont les rôles existent et dont les hôtes sont **actifs**.
|
||||
|
||||
Vérifié rôle par rôle contre `roles/`, ce que le document disait de faux :
|
||||
|
||||
| Ce qu'il annonçait | La réalité |
|
||||
|---|---|
|
||||
| collaboration « à implémenter » | `serveur_nextcloud` (533 lignes) + `serveur_collabora`, `collab-01` actif |
|
||||
| couche web « à implémenter » | `serveur_web_frontal` / `serveur_web_dorsal`, codifiés depuis les spikes du 5 juillet |
|
||||
| fédération LDAP « pas automatisée » | `serveur_keycloak/tasks/federation-ldap.yml` |
|
||||
| Keycloak « pas exposé » | `expose: auth.<domaine>` au plan |
|
||||
| `infra-mail-01` : « Sendmail MTA » | Dovecot — Sendmail est retiré depuis le 4 juillet |
|
||||
| `client_supervision` | n'a **jamais** existé : ni rôle, ni playbook |
|
||||
| rôles `nextcloud`, `metriques`… | une colonne décorative : le rôle porte le nom du **groupe** |
|
||||
|
||||
Et **neuf rôles vivants** n'apparaissaient dans aucune table — le socle, toute la pile
|
||||
courriel, les sauvegardes, Icinga Web 2, oauth2-proxy, Unbound. Deux d'entre eux
|
||||
(`serveur_backup`, `client_backup`) n'étaient nommés **nulle part** dans le document.
|
||||
|
||||
### Ce que P31 ne pouvait pas voir
|
||||
|
||||
La preuve de documentation vérifie que chaque script, cible `make` et rôle est **nommé et
|
||||
atteignable**. Elle ne dit rien de la justesse d'un document. **Une carte peut être
|
||||
complète et périmée** — celle-ci l'était depuis la consolidation du 3 juillet.
|
||||
|
||||
### P38 — la table fait foi, dans les deux sens
|
||||
|
||||
```
|
||||
tout rôle serveur_*/client_* doit figurer dans une LIGNE DE TABLE
|
||||
tout groupe cité dans une table doit exister (rôle, ou playbook de groupe)
|
||||
```
|
||||
|
||||
Le premier sens seul aurait été trop faible : la pile courriel **était** racontée en
|
||||
prose, et invisible pour qui lit le catalogue comme un index — c'est-à-dire tout le monde.
|
||||
Le second attrape les cases inventées, qui survivent des mois parce qu'une case de table
|
||||
ressemble à un fait.
|
||||
|
||||
Deux exemptions, nommées pour rester des choix : la **prose** peut citer des rôles retirés
|
||||
(`serveur_sendmail`, `client_dns`, `client_ldap`) — sinon on ne peut plus écrire d'où l'on
|
||||
vient ; et `serveur_durci` est accepté comme **groupe sans rôle homonyme**, son playbook
|
||||
composant onze rôles de durcissement.
|
||||
|
||||
**Éprouvée en négatif** : rejouée contre la version d'avant les corrections, elle échoue en
|
||||
nommant les neuf rôles absents et les trois cases fantômes. `make prouver` : **38 OK, 0
|
||||
échec, 0 sauté**.
|
||||
|
||||
> **Ce que la preuve ne mesure pas, et ne mesurera pas.** Qu'un service soit dit
|
||||
> « éprouvé » à bon droit se juge en revue, contre le CHANGELOG. Le tableau des capacités
|
||||
> dit maintenant lui-même où il s'arrête : la reconstruction prouve qu'une machine nue
|
||||
> atteint l'état voulu, elle ne dit rien de la tenue sous charge ni des mises à jour. Et
|
||||
> pour Nextcloud, l'usage réel — déposer un fichier, éditer à deux — n'est **pas** consigné
|
||||
> comme preuve ; le document le dit désormais au lieu de le laisser supposer.
|
||||
|
||||
|
||||
## 2026-08-18 — `make underlay` confronte les tenants déclarés aux dossiers réels
|
||||
|
||||
Suite immédiate du filtre de portée : `underlay.tenants` nomme des **dossiers frères**.
|
||||
|
|
|
|||
74
docs/audit/preuve-2026-08-19.md
Normal file
74
docs/audit/preuve-2026-08-19.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Preuve de conformite — Set-OPS — 2026-08-19
|
||||
|
||||
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (38 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | [0m[0m |
|
||||
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
|
||||
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 3 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ 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. |
|
||||
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 77 flux, schéma + matrice OK. |
|
||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
|
||||
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 26 secret(s) exige(s), tous presents. Voute reelle : 29 cle(s), aucun manque. |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
|
||||
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (20 sections). |
|
||||
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 6 reseau(x), aucune collision avec la plage tenant. |
|
||||
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 35 regles, 12 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. |
|
||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 42 groupe(s), 68 regle(s). |
|
||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 0 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 45 scripts expliques et atteignables, 93 cibles make documentees, 54 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 35 exigence(s) de role, toutes satisfaites (126 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 32 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 40 document(s) declarent leur lecteur (18 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (OPS-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 29 role(s) serveur/client tous nommes, 30 groupe(s) cite(s) en table existent tous. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||
|
||||
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||
|
||||
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||
comme declarations d'intention, non comme preuves :
|
||||
|
||||
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||
contre une flotte vivante.
|
||||
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||
|
||||
_Rapport genere le 2026-08-19._
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
# Catalogue des services
|
||||
|
||||
> **Pour qui :** le **mainteneur** — les noms de groupes et de playbooks des services, existants et à venir, avec leur maturité.
|
||||
> **Pour qui :** le **mainteneur** — ce que le moteur sait déployer, sous quel nom de groupe, et jusqu'où c'est éprouvé.
|
||||
|
||||
Ce document fixe les noms de groupes et de playbooks pour les prochains services.
|
||||
|
||||
La règle reste :
|
||||
Ce document nomme les services que le moteur **sait déployer**, et la règle qui lie un
|
||||
groupe à son playbook :
|
||||
|
||||
```text
|
||||
groupe opérationnel -> playbooks/groupes/<groupe>.yml
|
||||
groupe opérationnel -> playbooks/groupes/<groupe>.yml (P04 le prouve)
|
||||
```
|
||||
|
||||
Les playbooks ajoutés maintenant sont des points d'ancrage. Ils ne doivent pas installer un service tant que le rôle correspondant n'existe pas et que ses variables, secrets et prérequis sont documentés.
|
||||
Le **rôle porte le nom du groupe** : `serveur_keycloak`, `client_pki`. Il n'y a pas de nom
|
||||
court séparé. Seule exception, `serveur_durci` : un groupe dont le playbook **compose**
|
||||
onze rôles de durcissement (`hardening_packages`, `sysctl_hardening`, `apparmor`,
|
||||
`auditd`, `fail2ban_ssh`, `ssh_hardening`, `nftables_baseline`…) plutôt qu'un rôle
|
||||
homonyme.
|
||||
|
||||
La nomenclature des VM et des VMID est documentée dans `docs/nomenclature-vm.md`.
|
||||
|
||||
|
|
@ -20,75 +23,71 @@ Un service central peut partager un hôte avec d'autres services de la même fon
|
|||
|
||||
## État d'implémentation des rôles
|
||||
|
||||
> **Mise à jour (2026-06-24).** La plupart des rôles ci-dessous **existent désormais** —
|
||||
> écrits et validés *en tant que code*, **pas encore éprouvés sur des VM réelles**. Le rôle
|
||||
> effectif porte le nom du **groupe** (`serveur_keycloak`, `client_pki`…), pas le nom court
|
||||
> de la colonne « Rôle » des tables.
|
||||
> **Mise à jour (2026-08-18), vérifiée rôle par rôle contre `roles/`.** Les 29 groupes
|
||||
> `serveur_*` / `client_*` de ce catalogue ont **tous** leur rôle et leur playbook. Aucune
|
||||
> capacité annoncée ici n'est un point d'ancrage vide.
|
||||
|
||||
**Implémentés** (`tasks` + `templates` + `handlers`, validés) : `serveur_step_ca`,
|
||||
`client_pki`, `serveur_powerdns`, `serveur_openldap`,
|
||||
`serveur_keycloak`, `serveur_postgresql`, `serveur_redis`, `serveur_nginx`,
|
||||
`client_smtp`, `serveur_prometheus`, `client_metrique`, `serveur_loki`,
|
||||
`client_journal`, `serveur_grafana`, `serveur_icinga`, `serveur_forgejo`. Plus le socle et
|
||||
le durcissement, appliqués par `serveur_debian` / `serveur_durci`.
|
||||
**Éprouvés sur VM réelles.** Le 2026-08-13, la flotte a été **reconstruite depuis zéro**
|
||||
— 43 groupes, 0 échec, 37 minutes — puis remontée d'un seul trait. Ce n'est donc plus
|
||||
« du code validé » : chaque rôle a repris une machine nue et l'a menée à l'état voulu.
|
||||
|
||||
**Éprouvés sur VM réelles (mise à jour 2026-07-03)** — déployés et prouvés de bout en bout sur
|
||||
le cluster Proxmox (plus seulement « code ») : le **socle + durcissement**, `serveur_step_ca` +
|
||||
`client_pki` (mTLS avec SAN), `serveur_openldap` (LDAPS), `serveur_powerdns`, `serveur_nginx`,
|
||||
et la **pile courriel complète** `serveur_dovecot` + `serveur_postfix` + `serveur_rspamd`
|
||||
(flux SMTP→LDAP→LMTP→IMAP + antispam + DKIM prouvé). Plus, **le SSO et sa base** :
|
||||
`serveur_postgresql` (provisionne les bases du registre — **binding app→base prouvé**) et
|
||||
`serveur_keycloak` (26.0.7, mode prod, 87 tables écrites dans PostgreSQL, token admin obtenu).
|
||||
Plus l'**observabilité sous SSO** (2026-07-03) : `serveur_loki` + `serveur_prometheus` +
|
||||
`serveur_grafana` sur `obs-01`, avec **Grafana branché au SSO OIDC** (« Se connecter avec
|
||||
Chezlepro » prouvé : `testmail` LDAP → Keycloak → Grafana). Plus **`serveur_forgejo`** (10.0.0,
|
||||
sur `forge-01`, PostgreSQL via registre, **branché au SSO OIDC** — 2e app : `testmail` se connecte,
|
||||
compte auto-créé). Et **`serveur_redis`** (sur `data-sql-01`, cache réseau, auth obligatoire —
|
||||
SET/GET prouvé, accès sans mot de passe refusé). Et le **cœur `serveur_icinga`** (sur `sup-01` :
|
||||
icinga2 + icingadb + redis dédié, base PostgreSQL via registre — supervise, IcingaDB peuplée) +
|
||||
**`serveur_icingaweb2`** (UI native, PHP/nginx local, module IcingaDB + **module BPM**, **SSO Keycloak**
|
||||
via `serveur_oauth2_proxy` — `testmail` se connecte par le SSO, voit la supervision, et un processus
|
||||
métier BPM rend un état). **Pile Icinga complète : moteur + Web 2 + BPM, au SSO.** Plus la passerelle
|
||||
**`serveur_oauth2_proxy`** (oauth2-proxy) : SSO OIDC générique réutilisable pour toute app sans OIDC
|
||||
natif. Plus les **agents de flotte** `client_metrique` / `client_journal` / `client_smtp` (éprouvés
|
||||
sur 4 nœuds : Prometheus scrape la flotte, Loki collecte les logs, relais courrier système). **Tous
|
||||
les rôles vivants sont éprouvés.** *Rôles retirés (2026-07-04, supersédés/hors-conception)* :
|
||||
`serveur_sendmail` (→ Postfix), `client_dns` (→ plancher + `client_unbound`), `client_ldap` (login
|
||||
LDAP OS, hors design).
|
||||
Ce que la reconstruction couvre, par capacité :
|
||||
|
||||
> **Lacunes connues sur Keycloak** (déployé mais pas complet) : la **fédération LDAP** (Keycloak
|
||||
> → OpenLDAP, modèle d'identité A) n'est **pas encore automatisée** dans le rôle ; l'**edge nginx**
|
||||
> (Keycloak derrière le reverse-proxy TLS) n'est pas câblé. Keycloak tourne et s'authentifie, mais
|
||||
> n'est pas encore fédéré ni exposé. À faire pour clore le SSO.
|
||||
| Capacité | Rôles | Ce qui est éprouvé |
|
||||
|---|---|---|
|
||||
| Socle et durcissement | `serveur_debian`, `serveur_durci` | clone du gabarit doré → machine conforme |
|
||||
| Confiance | `serveur_step_ca`, `client_pki` | mTLS avec SAN dérivés du plan, renouvellement |
|
||||
| Noms | `serveur_powerdns`, `client_unbound` | autoritaire interne + résolveur local |
|
||||
| Identité | `serveur_openldap`, `serveur_keycloak` | LDAPS, SSO OIDC, **fédération LDAP automatisée** (`tasks/federation-ldap.yml`), exposé par le plan (`auth.<domaine>`) |
|
||||
| Passerelle SSO | `serveur_oauth2_proxy` | SSO devant une app sans OIDC natif (éprouvé sur Icinga Web 2) |
|
||||
| Données | `serveur_postgresql`, `serveur_redis` | bases et comptes dérivés du registre, TLS `verify-full` |
|
||||
| Courriel | `serveur_postfix`, `serveur_dovecot`, `serveur_rspamd` | SMTP→LDAP→LMTP→IMAP, antispam, DKIM |
|
||||
| Edge | `serveur_nginx` | terminaison TLS, publication des `expose:` du plan |
|
||||
| Observabilité | `serveur_prometheus`, `serveur_loki`, `serveur_grafana` | métriques, journaux, tableaux sous SSO |
|
||||
| Supervision | `serveur_icinga`, `serveur_icingaweb2` | Icinga 2 + IcingaDB + Web 2 + BPM, au SSO |
|
||||
| Forge | `serveur_forgejo` | Git + PostgreSQL + SSO OIDC |
|
||||
| Collaboration | `serveur_nextcloud`, `serveur_collabora` | déployés par la reconstruction, base et client OIDC dérivés du plan — **usage** (dépôt de fichier, édition partagée) non consigné comme preuve |
|
||||
| Plateforme webapp | `serveur_web_frontal`, `serveur_web_dorsal` | sites statiques et webapps natives (venv + systemd + nginx), **zéro conteneur** |
|
||||
| Sauvegardes | `serveur_backup`, `client_backup` | restic hors-nœud, **restauration éprouvée** (2026-08-12 : la donnée revient) |
|
||||
| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte |
|
||||
|
||||
**Cruft retirée (consolidation 2026-07-03).** Supprimés : les 8 dossiers-catégories inertes
|
||||
(`roles/{applications,backup,database,identity,monitoring,proxmox,storage,web}/`, README seuls,
|
||||
vestiges d'une taxonomie abandonnée — l'architecture réelle est **plate** `serveur_*` / `client_*`)
|
||||
et les 5 playbooks-échafaudages `debug` sans rôle (`serveur_nextcloud`, `serveur_collabora`,
|
||||
`client_supervision`, `serveur_web_frontal`, `serveur_web_dorsal`).
|
||||
*Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail`
|
||||
(→ Postfix), `client_dns` (→ plancher `/etc/hosts` + `client_unbound`), `client_ldap`
|
||||
(login LDAP au niveau OS, hors design).
|
||||
|
||||
**Capacités futures encore à implémenter** (intention, plus de code mort) : *collaboration*
|
||||
(Nextcloud/Collabora) et *couche web applicative* (frontal/dorsal). Le jour venu, elles se font
|
||||
en rôles `serveur_*` normaux avec leur playbook de groupe.
|
||||
> **Ce que « éprouvé » ne dit pas.** La reconstruction prouve que le moteur mène une
|
||||
> machine nue à l'état voulu. Elle ne dit rien de la tenue d'un service sous charge, ni de
|
||||
> sa mise à jour dans le temps — deux questions distinctes, et non traitées ici.
|
||||
|
||||
## Services centraux
|
||||
|
||||
| Service | Groupe | Playbook | Rôle |
|
||||
| --- | --- | --- | --- |
|
||||
| Keycloak | `serveur_keycloak` | `playbooks/groupes/serveur_keycloak.yml` | `keycloak` |
|
||||
| OpenLDAP | `serveur_openldap` | `playbooks/groupes/serveur_openldap.yml` | `openldap` |
|
||||
| PostgreSQL | `serveur_postgresql` | `playbooks/groupes/serveur_postgresql.yml` | `postgresql` |
|
||||
| Loki | `serveur_loki` | `playbooks/groupes/serveur_loki.yml` | `loki` |
|
||||
| Plateforme Icinga | `serveur_icinga` | `playbooks/groupes/serveur_icinga.yml` | `icinga` |
|
||||
| Prometheus | `serveur_prometheus` | `playbooks/groupes/serveur_prometheus.yml` | `prometheus` |
|
||||
| Grafana | `serveur_grafana` | `playbooks/groupes/serveur_grafana.yml` | `grafana` |
|
||||
| Forgejo | `serveur_forgejo` | `playbooks/groupes/serveur_forgejo.yml` | `forgejo` |
|
||||
| step-ca | `serveur_step_ca` | `playbooks/groupes/serveur_step_ca.yml` | `step_ca` |
|
||||
| PowerDNS | `serveur_powerdns` | `playbooks/groupes/serveur_powerdns.yml` | `serveur_powerdns` |
|
||||
| Redis | `serveur_redis` | `playbooks/groupes/serveur_redis.yml` | `redis` |
|
||||
| NGINX WAF et reverse proxy | `serveur_nginx` | `playbooks/groupes/serveur_nginx.yml` | `nginx` |
|
||||
| Nextcloud | `serveur_nextcloud` | `playbooks/groupes/serveur_nextcloud.yml` | `nextcloud` |
|
||||
| Collabora | `serveur_collabora` | `playbooks/groupes/serveur_collabora.yml` | `collabora` |
|
||||
Le playbook est toujours `playbooks/groupes/<groupe>.yml`, et le rôle porte le nom du
|
||||
groupe : la table ne répète donc ni l'un ni l'autre.
|
||||
|
||||
| Service | Groupe |
|
||||
| --- | --- |
|
||||
| Socle Debian | `serveur_debian` |
|
||||
| Durcissement (compose onze rôles) | `serveur_durci` |
|
||||
| step-ca (AC interne + ACME) | `serveur_step_ca` |
|
||||
| PowerDNS (autoritaire interne) | `serveur_powerdns` |
|
||||
| OpenLDAP (source de vérité des identités) | `serveur_openldap` |
|
||||
| Keycloak (SSO OIDC, fédéré à l'annuaire) | `serveur_keycloak` |
|
||||
| oauth2-proxy (SSO devant une app sans OIDC natif) | `serveur_oauth2_proxy` |
|
||||
| PostgreSQL | `serveur_postgresql` |
|
||||
| Redis | `serveur_redis` |
|
||||
| NGINX — edge : TLS, mandataire inverse, WAF | `serveur_nginx` |
|
||||
| Postfix (MTA) | `serveur_postfix` |
|
||||
| Dovecot (mailstore IMAP/LMTP) | `serveur_dovecot` |
|
||||
| rspamd (antispam, DKIM) | `serveur_rspamd` |
|
||||
| Prometheus | `serveur_prometheus` |
|
||||
| Loki | `serveur_loki` |
|
||||
| Grafana | `serveur_grafana` |
|
||||
| Icinga 2 + IcingaDB | `serveur_icinga` |
|
||||
| Icinga Web 2 (+ module BPM) | `serveur_icingaweb2` |
|
||||
| Forgejo | `serveur_forgejo` |
|
||||
| Nextcloud | `serveur_nextcloud` |
|
||||
| Collabora | `serveur_collabora` |
|
||||
| Dépôt de sauvegarde restic | `serveur_backup` |
|
||||
|
||||
## Couche applicative web
|
||||
|
||||
|
|
@ -100,41 +99,82 @@ La couche applicative web est distincte de l'edge `serveur_nginx` :
|
|||
|
||||
Le « web » de ces deux groupes est implicite par leur position derrière `serveur_nginx`. Le jour où un service dorsal n'est pas web (worker batch, file, démon), créer un groupe dédié plutôt que d'élargir `serveur_web_dorsal`.
|
||||
|
||||
| Couche | Groupe | Playbook | Rôle |
|
||||
| --- | --- | --- | --- |
|
||||
| Présentation web (frontends) | `serveur_web_frontal` | `playbooks/groupes/serveur_web_frontal.yml` | `web_frontaux` |
|
||||
| Application web (backends) | `serveur_web_dorsal` | `playbooks/groupes/serveur_web_dorsal.yml` | `web_dorsaux` |
|
||||
| Couche | Groupe | Ce que le rôle pose |
|
||||
| --- | --- | --- |
|
||||
| Présentation web (sites statiques) | `serveur_web_frontal` | nginx + contenu tiré d'un dépôt git souverain (Forgejo), en-têtes de sécurité |
|
||||
| Application web (webapps dynamiques) | `serveur_web_dorsal` | runtime + service systemd + nginx local — venv/paquets, **zéro conteneur** |
|
||||
|
||||
Hôtes planifiés : `web-frontal-01` et `web-frontal-02` dans `serveur_web_frontal` ; `web-dorsal-01` dans `serveur_web_dorsal`. Aucun n'est encore actif (à créer puis activer).
|
||||
Les deux rôles sont **codifiés depuis les spikes du 2026-07-05** (site Alliance Boréale
|
||||
pour le frontal ; monregistraire — FastAPI + uvicorn + SQLite — pour le dorsal), et
|
||||
`web-frontal-01` / `web-dorsal-01` sont **actifs** dans le plan de référence.
|
||||
|
||||
Dépendance d'intégration prévue : `serveur_web_frontal` devra publier via `serveur_nginx` (règle « tout service exposé en HTTP(S) passe par l'edge »). Cette dépendance n'est pas encore déclarée dans `docs/dependances-groupes.yml` (les deux groupes sont vides) ; elle sera ajoutée avec le rôle `web_frontaux`, lorsque des frontaux actifs devront publier via l'edge.
|
||||
L'exposition ne passe pas par une dépendance de groupe mais par le **plan** : le champ
|
||||
`expose:` d'une application fait dériver le vhost de l'edge, les SAN de son certificat et
|
||||
le plancher `/etc/hosts`. Une seule ligne au plan, aucune recopie.
|
||||
|
||||
## Hôtes planifiés
|
||||
> **Lacune nommée** : la dépendance causale `serveur_web_frontal` → `serveur_nginx` n'est
|
||||
> toujours **pas déclarée** dans `docs/dependances-groupes.yml`. Le déploiement d'un
|
||||
> frontal n'est donc pas refusé quand l'edge est absent — il aboutit à un site que rien ne
|
||||
> publie. À déclarer.
|
||||
|
||||
| Hôte | Services |
|
||||
## Hôtes du plan de référence
|
||||
|
||||
Les quatorze hôtes de l'écosystème de référence, **tous à l'état `actif`**, tels que les
|
||||
déclare `instance/plan/applications.yml`. Un modèle plus petit en porte moins : cette
|
||||
table décrit une répartition éprouvée, pas un minimum requis.
|
||||
|
||||
| Hôte | Applications |
|
||||
| --- | --- |
|
||||
| `infra-pki-01` | step-ca |
|
||||
| `infra-edge-01` | NGINX WAF et reverse proxy |
|
||||
| `infra-mail-01` | Sendmail MTA |
|
||||
| `infra-dns-01` | PowerDNS |
|
||||
| `infra-edge-01` | NGINX (edge) |
|
||||
| `edge-mta-01` | Postfix, rspamd |
|
||||
| `infra-mail-01` | Dovecot |
|
||||
| `idm-01` | OpenLDAP, Keycloak |
|
||||
| `data-01` | PostgreSQL, Redis |
|
||||
| `data-sql-01` | PostgreSQL, Redis |
|
||||
| `obs-01` | Prometheus, Loki, Grafana |
|
||||
| `mon-01` | Plateforme Icinga : Icinga 2, Icinga Web 2, Icinga BPM |
|
||||
| `mon-01` | Icinga 2, Icinga Web 2, oauth2-proxy |
|
||||
| `forge-01` | Forgejo |
|
||||
| `collab-01` | Nextcloud, Collabora |
|
||||
| `backup-01` | dépôt restic |
|
||||
| `web-frontal-01` | site statique |
|
||||
| `web-dorsal-01` | webapp native |
|
||||
|
||||
Le courriel occupe **deux** hôtes, et ce n'est pas un détail de taille : `edge-mta-01`
|
||||
porte ce qui parle à l'extérieur (Postfix, rspamd), `infra-mail-01` ce qui détient les
|
||||
boîtes (Dovecot). La coupure suit l'exposition, pas le logiciel.
|
||||
|
||||
## Intégrations clientes
|
||||
|
||||
| Intégration | Groupe | Playbook | Rôle |
|
||||
| --- | --- | --- | --- |
|
||||
| Confiance PKI / ACME | `client_pki` | `playbooks/groupes/client_pki.yml` | `client_pki` |
|
||||
| Supervision Icinga | `client_supervision` | `playbooks/groupes/client_supervision.yml` | `client_supervision` |
|
||||
| Métriques Prometheus | `client_metrique` | `playbooks/groupes/client_metrique.yml` | `client_metriques` |
|
||||
| Journaux vers Loki | `client_journal` | `playbooks/groupes/client_journal.yml` | `client_journaux` |
|
||||
| Relais SMTP | `client_smtp` | `playbooks/groupes/client_smtp.yml` | `client_smtp` |
|
||||
| Intégration | Groupe | Posée sur |
|
||||
| --- | --- | --- |
|
||||
| Confiance PKI / ACME | `client_pki` | **tout hôte** (universelle) |
|
||||
| Métriques Prometheus | `client_metrique` | **tout hôte** (universelle) |
|
||||
| Journaux vers Loki | `client_journal` | **tout hôte** (universelle) |
|
||||
| Résolution locale (Unbound) | `client_unbound` | **tout hôte** (universelle) |
|
||||
| Relais SMTP | `client_smtp` | déclaré par hôte, dans le plan |
|
||||
| Sauvegarde restic | `client_backup` | déclaré par hôte — **obligatoire pour tout détenteur d'état** (P36) |
|
||||
|
||||
## Ordre d'implémentation recommandé
|
||||
**Universelle** ne veut pas dire recopiée : une intégration marquée `universelle: true`
|
||||
dans son `meta/` est posée sur chaque hôte par dérivation, avec l'exemption dérivée du
|
||||
service rendu (P26 : un hôte n'est pas client de ce qu'il sert). Aucune ligne à écrire
|
||||
dans le plan.
|
||||
|
||||
> **`client_supervision` n'existe pas.** Ce catalogue l'a longtemps annoncé ; il n'a
|
||||
> jamais eu ni rôle ni playbook, et rien ne l'attend. La supervision s'exerce **sans agent
|
||||
> sur les hôtes** : contrôles actifs depuis le cœur (`hostalive`) et résultats **passifs
|
||||
> poussés par l'API** par celui qui détient la vérité de terrain — ainsi l'état des
|
||||
> sauvegardes est-il rapporté par `backup-01`, seul à pouvoir lire ses dépôts. Le nom est
|
||||
> retiré plutôt que réservé : une case vide dans un catalogue se lit comme une promesse.
|
||||
|
||||
## Ordre de déploiement — le raisonnement
|
||||
|
||||
> **L'ordre exécutable n'est pas ici.** Il vit dans `docs/couches-deploiement.yml` et
|
||||
> `docs/dependances-groupes.yml`, que P08 prouve cohérents (30 groupes classés, aucun
|
||||
> cycle, aucune arête en arrière) et que `make reconstruire` suit. Ce qui suit en est le
|
||||
> **raisonnement**, utile pour comprendre pourquoi cet ordre-là — et pour placer un
|
||||
> service nouveau. Les phases sont franchies : les « intégrations à prévoir » ci-dessous
|
||||
> sont posées.
|
||||
|
||||
L'ordre ci-dessous privilégie les dépendances structurantes avant les applications.
|
||||
|
||||
|
|
@ -195,11 +235,11 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
|
|||
|
||||
### Phase 4 - Supervision active
|
||||
|
||||
11. `serveur_icinga`
|
||||
- Service central : supervision active, interface web et vues métiers Icinga.
|
||||
- Composants prévus : Icinga 2, Icinga Web 2, Icinga BPM.
|
||||
- Intégrations à prévoir : `client_supervision`, PostgreSQL, NGINX, Keycloak si retenu.
|
||||
- Raison : ces composants forment une même capacité de supervision et gagnent à cohabiter sur `mon-01` au départ.
|
||||
11. `serveur_icinga`, puis `serveur_icingaweb2`
|
||||
- Service central : supervision active, interface web et vues métiers (BPM).
|
||||
- Intégrations : PostgreSQL, NGINX, et le SSO **via `serveur_oauth2_proxy`** — Icinga Web 2 n'a pas d'OIDC natif.
|
||||
- Rien à poser sur les hôtes supervisés : contrôles actifs depuis le cœur, résultats passifs poussés par l'API.
|
||||
- Raison : ces composants forment une même capacité de supervision et cohabitent sur `mon-01`.
|
||||
|
||||
### Phase 5 - Services applicatifs internes
|
||||
|
||||
|
|
@ -228,7 +268,8 @@ L'ordre ci-dessous privilégie les dépendances structurantes avant les applicat
|
|||
- Tout service exposé en HTTP(S) doit prévoir son intégration avec `serveur_nginx`.
|
||||
- Tout service avec authentification humaine doit prévoir son intégration avec `serveur_keycloak`, sauf justification contraire.
|
||||
- Tout service générant des alertes ou notifications doit prévoir `client_smtp`.
|
||||
- Toute VM de service doit rejoindre `client_metrique`, `client_journal` et `client_supervision` quand les services centraux correspondants existent.
|
||||
- Toute VM de service rejoint `client_pki`, `client_metrique`, `client_journal` et `client_unbound` — **par dérivation, sans rien écrire** (intégrations universelles, P26). La supervision, elle, ne pose rien sur l'hôte.
|
||||
- Tout hôte qui **détient de l'état** doit porter `client_backup` (P36 le refuse sinon).
|
||||
- Tout service utilisant un certificat interne doit dépendre de `client_pki`.
|
||||
- Tout rôle serveur doit documenter ses ports, secrets, sauvegardes, dépendances et groupes clients associés.
|
||||
|
||||
|
|
|
|||
|
|
@ -705,6 +705,69 @@ def preuve_lecteur_declare() -> tuple[bool, str]:
|
|||
f"({generes} genere(s) exempte(s)).")
|
||||
|
||||
|
||||
def preuve_catalogue_a_jour() -> tuple[bool, str]:
|
||||
"""Le catalogue des services nomme TOUT ce que le moteur sait deployer, et rien d'autre.
|
||||
|
||||
POURQUOI (mesure du 2026-08-18). `catalogue-services.md` est la carte de ce que Set-OPS
|
||||
FAIT — le document que lit l'hebergeur d'un second site, un futur client, un mainteneur
|
||||
qui arrive. Il avait quatre mois de retard : il annoncait comme « capacites futures
|
||||
encore a implementer » la collaboration (Nextcloud/Collabora) et la couche web
|
||||
(frontal/dorsal), dont les roles existent et dont les hotes sont ACTIFS ; il disait la
|
||||
federation LDAP de Keycloak « pas encore automatisee » alors que le role la pose ; ses
|
||||
tables ignoraient NEUF roles vivants (le socle, la pile courriel entiere, les
|
||||
sauvegardes, Icinga Web 2, oauth2-proxy, Unbound) dont deux — `serveur_backup` et
|
||||
`client_backup` — que le document ne nommait NULLE PART ; et il annoncait une
|
||||
integration `client_supervision`
|
||||
qui n'a jamais existe, plus deux roles inventes par une colonne « Role » decorative
|
||||
(`client_metriques`, `client_journaux`, au pluriel).
|
||||
|
||||
P31 ne pouvait pas le voir : elle verifie que chaque script, cible `make` et role est
|
||||
NOMME et ATTEIGNABLE — pas qu'un document dise vrai. Une carte peut etre complete et
|
||||
perimee.
|
||||
|
||||
CE QU'ELLE TESTE, dans les deux sens — et des DEUX cotes c'est la TABLE qui fait foi :
|
||||
- tout role `serveur_*` / `client_*` figure dans une ligne de table. Etre cite dans
|
||||
un paragraphe ne suffit pas : la pile courriel (`serveur_postfix`,
|
||||
`serveur_dovecot`, `serveur_rspamd`) etait racontee en prose et absente de toutes
|
||||
les tables — introuvable pour qui lit le catalogue comme un index, c'est-a-dire
|
||||
pour tout le monde ;
|
||||
- tout groupe cite dans une LIGNE DE TABLE du catalogue existe reellement — comme
|
||||
role, ou comme playbook de groupe (`serveur_durci` compose des roles de
|
||||
durcissement sans role homonyme). Une case de table est une affirmation
|
||||
d'existence : c'est ainsi que `client_supervision` a survecu des mois.
|
||||
|
||||
CE QU'ELLE NE TESTE PAS, et c'est deliberate :
|
||||
- la PROSE. Le catalogue raconte son histoire, y compris les roles RETIRES
|
||||
(`serveur_sendmail`, `client_dns`, `client_ldap`) : les nommer hors table doit
|
||||
rester permis, sinon on ne peut plus ecrire d'ou l'on vient ;
|
||||
- que la description soit JUSTE. Qu'un service soit dit « eprouve » a bon droit se
|
||||
juge en revue, contre le CHANGELOG. Mecaniser ce jugement serait se mentir.
|
||||
"""
|
||||
catalogue = RACINE / "docs" / "catalogue-services.md"
|
||||
if not catalogue.is_file():
|
||||
return False, "docs/catalogue-services.md absent"
|
||||
texte = catalogue.read_text(encoding="utf-8", errors="ignore")
|
||||
roles = {d.name for d in (RACINE / "roles").iterdir()
|
||||
if d.is_dir() and d.name.startswith(("serveur_", "client_"))}
|
||||
groupes = {p.stem for p in (RACINE / "playbooks" / "groupes").glob("*.yml")}
|
||||
|
||||
lignes_table = [l for l in texte.splitlines() if l.lstrip().startswith("|")]
|
||||
cites = {m for l in lignes_table for m in re.findall(r"(?:serveur|client)_[a-z0-9_]+", l)}
|
||||
tus = sorted(r for r in roles if r not in cites)
|
||||
fantomes = sorted(c for c in cites if c not in roles and c not in groupes)
|
||||
|
||||
echecs = []
|
||||
if tus:
|
||||
echecs.append(f"{len(tus)} role(s) que le catalogue ne nomme pas : " + ", ".join(tus))
|
||||
if fantomes:
|
||||
echecs.append(f"{len(fantomes)} groupe(s) annonce(s) en table sans role ni playbook : "
|
||||
+ ", ".join(fantomes))
|
||||
if echecs:
|
||||
return False, " | ".join(echecs)
|
||||
return True, (f"Catalogue a jour : {len(roles)} role(s) serveur/client tous nommes, "
|
||||
f"{len(cites)} groupe(s) cite(s) en table existent tous.")
|
||||
|
||||
|
||||
def preuve_documentation_outillage() -> tuple[bool, str]:
|
||||
"""Tout ce que Set-OPS FAIT s'explique et reste atteignable.
|
||||
|
||||
|
|
@ -899,6 +962,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_etat_sauvegarde},
|
||||
{"id": "P37", "titre": "Le placement du tenant existe chez son hebergeur", "refs": [],
|
||||
"func": preuve_placement_chez_hebergeur},
|
||||
{"id": "P38", "titre": "Catalogue des services : la carte dit ce que le moteur fait", "refs": [],
|
||||
"func": preuve_catalogue_a_jour},
|
||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue