Sur un trunk convergé (tenants partagent l'infra), les VLAN doivent être uniques GLOBALEMENT. deriver_nomenclature (ip-miroir) dérive VLAN = index×10 + zone : chaque tenant son bloc de 10, 1-9 réservés à l'infra partagée. Le VLAN contenant le tenant, le VMID mène avec le VLAN (VLAN·octet·seq, ≤ 9 chiffres Proxmox). compact (lab) inchangé. GUI (miroir) + validation 1-4094 : compatibles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
717 lines
58 KiB
Markdown
717 lines
58 KiB
Markdown
# CHANGELOG — Set-OPS
|
||
|
||
## 2026-07-05
|
||
|
||
### Modifié
|
||
- **VLAN dérivé du tenant (réseau convergé).** `deriver_nomenclature` (schéma `ip-miroir`) dérive
|
||
désormais le VLAN = `index × 10 + zone` — **unique globalement** sur un trunk convergé (chaque
|
||
tenant son bloc de 10 ; 1-9 réservés à l'infra partagée). Le VLAN contenant déjà le tenant (1er
|
||
chiffre = index), le VMID **mène avec le VLAN** (`VLAN·octet·seq`, ≤ 9 chiffres Proxmox). Ex.
|
||
Technolibre (index 2) → VLANs 21-26, VMID 2101101. Le schéma `compact` (lab, sandbox) reste
|
||
inchangé. Champs `vlan:` codés en dur retirés des catégories ip-miroir (désormais dérivés).
|
||
|
||
### Ajouté
|
||
- **Zéro-confiance est-ouest — flux Métriques, Logs et Courriel chiffrés.** Suite du chantier
|
||
(après PostgreSQL) : **métriques** (node_exporter sert en HTTPS via cert step-ca +
|
||
`--web.config.file` + cert-sync owned prometheus ; Prometheus scrape `scheme: https` +
|
||
`tls_config`), **logs** (Loki `http_tls_config` + cert-sync owned loki ; Alloy push `https` +
|
||
`tls_config`), **courriel** (LMTP `edge-mta→infra-mail:24` en `lmtp_tls_security_level=verify` +
|
||
`lmtp_tls_CAfile` ; `client_smtp` en STARTTLS vérifié). Chacun prouvé de bout en bout (200 HTTPS,
|
||
cibles UP, livraison `status=sent`, HTTP rejeté). Motif cert-sync `.path` industrialisé. Reste :
|
||
edge→backends + DNS (DoT).
|
||
- **VMID 9 chiffres mnémotechnique (schéma `ip-miroir`, opt-in).** `vmid_schema: ip-miroir` dans la
|
||
nomenclature → VMID `I·VVV·HHH·NN` (index·VLAN·octet-hôte·séquence) : le VMID *contient* l'IP
|
||
(`10.(10+index).VLAN.hôte`) + le tenant, lisible d'un coup d'œil. Défaut `compact` rétro-compatible
|
||
(instances déployées inchangées).
|
||
- **Instance partenaire Technolibre.** Écosystème complet (12 VM, `etat: planifie`) dans
|
||
`10.12.16.0/20`, **6 zones de sécurité** (Frontière/Identité/Données/Services-infra/Observabilité/
|
||
Applications, un /24 + VLAN chacune), index de fédération 2, VMID ip-miroir. Preuve de portabilité
|
||
d'un tenant.
|
||
|
||
### Modifié
|
||
- **Références par FQDN partout (fin des IP codées en dur).** Décision d'archi : FQDN pour toute
|
||
référence inter-services (non ambigu en fédération, canonique pour TLS ; nom court = hostname OS).
|
||
`resoudre_base` renvoie le FQDN (→ keycloak/forgejo/icinga) ; `client_journal_loki_url` dérivé du
|
||
groupe `serveur_loki` ; defaults db_host IP morts nettoyés. **Aucune IP littérale dans les defaults.**
|
||
- **Découplage du tenant d'origine.** Realm SSO centralisé sur l'intrant **`identite_realm`** (défaut
|
||
`chezlepro` ; les 4 rôles keycloak/forgejo/grafana/oauth2_proxy en dérivent ; exposé dans la GUI).
|
||
Vars brandées renommées génériques : `chezlepro_timezone→fuseau_horaire`,
|
||
`chezlepro_organisation→organisation`. Le moteur ne porte plus le nom d'un tenant.
|
||
- **Modèles d'instance rafraîchis.** `integral` régénéré depuis le cas prouvé (6 zones, fonctions
|
||
éprouvées `data-sql`/`id-ldap`/`id-sso`/`sup`, ip-miroir, nouveaux intrants) ; `socle`/`identite`/
|
||
`observabilite`/`forge` réalignés sur le même moule (prouvés : dérivation + `valider_serveurs`).
|
||
`presence-web` marqué **aspirationnel** (rôles web-frontal/dorsal absents) plutôt que faussement prêt.
|
||
|
||
## 2026-07-04
|
||
|
||
### Ajouté
|
||
- **Zéro-confiance : flux PostgreSQL entièrement chiffré et vérifié.** PG sert désormais son **cert
|
||
step-ca** (vérifiable contre root_ca) au lieu du snakeoil, et **refuse** toute connexion non-TLS
|
||
du réseau (`hostssl` dans pg_hba). Les 3 clients passent en **verify-full** : keycloak
|
||
(`db-url-properties sslmode=verify-full`), forgejo (`SSL_MODE=verify-full` + `PGSSLROOTCERT`),
|
||
IcingaDB (`tls: true` + `ca`). Prérequis posés : `client_pki` sur data-sql-01 (cert), et
|
||
**`root_ca.crt` en 0644** (cert public, requis par les clients TLS non-root). cert-sync PG
|
||
(motif `.path`, owned postgres) + reload de l'instance `postgresql@NN-main`. Vars :
|
||
`serveur_postgresql_tls_actif`/`_tls_force`, `serveur_*_db_sslmode`/`_ca`. Prouvé de bout en bout
|
||
(cert Set-OPS CA servi, apps 200, non-TLS rejeté « aucun chiffrement », TLS accepté).
|
||
|
||
### Corrigé
|
||
- **PG : détection de version robuste (collision avec un répertoire non-numérique).** Placer le
|
||
`tls_dir` sous `/etc/postgresql/` faisait choisir `tls` comme « version » de cluster
|
||
(`find | sort | last`) → configs déployées au mauvais endroit (verrou hostssl inopérant).
|
||
Corrigé : détection filtrée aux dossiers **numériques** (`^[0-9]+$`) + `tls_dir` déplacé sous
|
||
`/var/lib/postgresql/tls`.
|
||
- **Renouvellement de cert : recharger le VRAI consommateur (bug latent de flotte).** Le
|
||
`cert-renewer@.service` (client_pki) renouvelait le cert sur disque mais son `ExecStartPost`
|
||
rechargeait un service nommé *d'après le cert* (`%i` = FQDN), inexistant → **nginx (et postfix,
|
||
dovecot, slapd) n'étaient jamais rechargés** et servaient l'**ancien cert jusqu'à expiration**.
|
||
Symptôme vécu : cert edge expiré en mémoire (renouvelé sur disque), échec TLS de l'échange
|
||
code→jeton OIDC → **login Grafana/SSO cassé** (tous les services derrière l'edge). Correctif :
|
||
`client_pki_reload_services` (liste des vrais consommateurs), câblée par groupe (edge→nginx,
|
||
mail→postfix/dovecot, annuaire→slapd). Appliqué + vérifié sur les 4 hôtes. Fix immédiat de
|
||
l'incident : `systemctl reload nginx` sur l'edge.
|
||
|
||
### Ajouté
|
||
- **Doc à jour : unité wiki « Autorisation & RBAC », leçon renouvellement, runbooks.** Fermeture
|
||
des dettes de doc : nouvelle **unité wiki authZ/RBAC** (pendant d'*Identité & SSO*, avec l'exemple
|
||
Grafana), **section « le renouvellement est un système »** versée dans l'unité *PKI* (comparer cert
|
||
servi vs fichier ; recharger le consommateur), et **`docs/runbooks-exploitation.md`** (cert expiré,
|
||
RBAC Grafana, branding Forgejo). 15 unités wiki désormais.
|
||
- **UI des logs Loki : dashboard Grafana provisionné.** Loki n'a pas d'UI ; son UI est Grafana.
|
||
Ajout d'un **dashboard « Journaux de la flotte »** (dossier Set-OPS) : sélecteur d'hôte multi +
|
||
filtre regex insensible à la casse + panneau logs + débit par hôte. Référence Loki par une
|
||
**variable de datasource** (`ds_loki`), pas un UID codé en dur (leçon : ajouter un uid explicite à
|
||
une datasource déjà provisionnée casse le démarrage de Grafana). **Visible par les Viewers** (dont
|
||
testmail) sans accès Explore. Prouvé sur obs-01 (dashboard chargé, Grafana actif).
|
||
- **RBAC via SSO : rôle de realm → niveau Grafana.** Machinerie *additive et idempotente* dans
|
||
`serveur_keycloak` (`rbac-oidc.yml`) : rôles de realm (`serveur_keycloak_realm_roles`), **mapper
|
||
`roles`** sur les clients choisis (`_role_mapper_clients`, rôles de realm → claim `roles` dans
|
||
ID token + userinfo), **assignations** rôle→utilisateur (`_role_assignments`). Côté Grafana,
|
||
`role_attribute_path` (`grafana-admin→Admin`, `grafana-editor→Editor`, sinon Viewer). kcadm **à
|
||
chaud, zéro coupure SSO**. Prouvé (idempotence `changed=0`) sur id-sso-01 : rôles créés, mapper
|
||
présent, `testmail` = `grafana-editor` (→ Explore). Illustre l'**authZ** (vs authN du SSO).
|
||
|
||
## 2026-07-03
|
||
|
||
### Ajouté
|
||
- **Identité visuelle Alliance Boréale sur Forgejo (léger, officiel).** Branding via le dossier
|
||
**`custom/`** de Forgejo (mécanisme *officiel* — pas de fork, résistant aux MAJ) : accent **aurore
|
||
par variables CSS** (`--color-primary`…, aucune classe interne touchée), **logo/favicon étoile**
|
||
(réutilisés du thème Keycloak), **page d'accueil brandée** (`home.tmpl` : hero aurore + accroche),
|
||
**thème sombre** par défaut, **nom + méta**. Codifié dans `serveur_forgejo`
|
||
(`serveur_forgejo_branding`, `_app_name`, `_theme`), déployé dans `{{ data }}/custom/`. **Prouvé**
|
||
sur forge-01 : accueil rend (200, « Forge Chezlepro »), `alliance.css` servi (cyan aurore), lint OK.
|
||
- **Wiki pédagogique Forgejo — 14 unités d'apprentissage.** Set-OPS comme *compagnon pédagogique* :
|
||
chaque service = une lentille sur un fondamental TIC, méthodes génériques (on apprend OIDC, pas
|
||
Keycloak). Source versionnée dans `wiki/`, publiée dans le wiki Forgejo (`eregion`). Moule à 4
|
||
temps (concept → Set-OPS → transférable → à toi de jouer, avec *casse-répare*).
|
||
- **GUI : boutons cohérents.** « Pousser » (surchargé : clonait *et* déployait) → « 🖥 Créer la VM »
|
||
pour le clone, « Déployer » partout pour le déploiement, dry-run obligatoire partout. GUI 100 %
|
||
française.
|
||
- **Agents d'observabilité/ops éprouvés — observabilité *flotte-complète*.** Les 3 intégrations
|
||
« agent » (liaisons nœud × optionnelles) déployées sur 4 nœuds (obs-01, data-sql-01, id-ldap-01,
|
||
forge-01) et **prouvées** : `client_metrique` (node_exporter → **Prometheus scrape les 4 cibles,
|
||
toutes UP**) ; `client_journal` (journald → **Loki reçoit les logs des 4 nœuds**) ; `client_smtp`
|
||
(msmtp → **courriel système d'un nœud relayé par l'edge-MTA et livré**). Comble le trou : les
|
||
*serveurs* d'observabilité (Grafana/Prometheus/Loki) étaient prouvés, mais pas la collecte
|
||
fleet-wide — désormais Grafana voit toute la flotte. Cibles corrigées (bac à sable) : Loki→obs-01,
|
||
relais→edge-mta-01 (pas infra-mail-01, qui est le store Dovecot sans SMTP :25).
|
||
- **Thème de connexion Keycloak à l'identité Alliance Boréale.** Thème de login `alliance-boreale`
|
||
(`roles/serveur_keycloak/files/themes/`, `parent=keycloak` + overlay CSS) reprenant l'identité du
|
||
site de l'Alliance (extraite de `site-alliance-boreale`) : **ciel nocturne aurore** (`#05060f`/
|
||
`#0a0d24` + dégradés), **carte glassmorphism**, **logo étoile aurore** (le `favicon.svg` du site),
|
||
**bouton dégradé aurore** (teal→cyan, pilule), liens cyan, **police système** (souveraineté, zéro
|
||
dépendance externe). Déployé dans `{{ keycloak_home }}/themes/`, appliqué au realm via
|
||
`kcadm ... -s loginTheme` (var `serveur_keycloak_login_theme`, idempotent), Keycloak rechargé
|
||
(`flush_handlers` avant la config realm). **Prouvé** : la page de login charge `alliance.css`
|
||
(HTTP 200) + le `logo.svg` (200), `loginTheme=alliance-boreale` actif sur `chezlepro`.
|
||
**Constellation animée en fond** (`scripts=js/constellation.js`) : le JS crée son propre ciel
|
||
(canvas + aurore, le template n'en ayant pas) — étoiles scintillantes qui dérivent, liens de
|
||
constellation cyan, blob d'aurore ondulant ; respecte `prefers-reduced-motion`. Prouvé :
|
||
`constellation.js` référencé + servi (200).
|
||
**Console de compte thémée aussi** (thème `account`, `parent=keycloak.v3`) : overlay CSS
|
||
surchargeant les variables PatternFly 5 (fond aurore, cartes en verre, accent aurore) + la même
|
||
constellation animée. Var `serveur_keycloak_account_theme` via `kcadm -s accountTheme`. Prouvé :
|
||
console charge (HTTP 200, `keycloak.v3` intact), `account.css` servi (200).
|
||
- **Soumission courriel `:587` interne (authentifiée) — la boucle souveraine est bouclée.**
|
||
Postfix (`edge-mta`) sert la **soumission `:587`** (bloc `master.cf` : STARTTLS requis, `SMTP AUTH`,
|
||
seuls les authentifiés relaient) ; l'auth SASL est **déléguée à Dovecot** (`infra-mail`, passdb LDAP
|
||
prouvé) via un **auth-listener réseau** (`service auth { inet_listener sasl }`, port 12345). Aucune
|
||
sortie internet : interne→interne uniquement (l'externe = déliverabilité, Étape B). **Prouvé** (swaks) :
|
||
`testmail` s'authentifie (`235 Authentication successful`), Postfix accepte (`250 queued`), et le
|
||
courriel est **livré dans la boîte** (LMTP→Dovecot). Le courriel souverain fait maintenant **recevoir
|
||
ET envoyer**. Vars : `serveur_dovecot_sasl_reseau`, `serveur_postfix_submission_actif`. Pièges :
|
||
Dovecot 2.4 exige un **nom** de section `inet_listener` ; ajouter un service `master.cf` (nouveau
|
||
listener) → handler **restart** (pas reload) ; et une config cassée peut bloquer un redéploiement si
|
||
la synchro cert/restart précède le template (corriger la config à la main pour débloquer).
|
||
- **Sauvegardes applicatives (logiques) — `serveur_backup` + `client_backup` (restic), Tier 0 prouvé.**
|
||
Choix : sauvegarder la **donnée d'état** (non régénérable) plutôt que les VM (reconstructibles par
|
||
le code + le template). Outil **restic** (chiffrement côté client, déduplication, rétention).
|
||
`serveur_backup` (nœud `backup-01`) = cible SFTP/SSH (utilisateur `restic`, clé autorisée, dépôts
|
||
sous `/srv/restic/<nœud>`). `client_backup` (intégration par nœud) = restic + **jobs déclaratifs**
|
||
(`client_backup_jobs` : `{nom, commande?, chemins}`), clé SSH + mot de passe restic en voûte,
|
||
script + **timer systemd** (quotidien) + rétention `forget --prune`. **Prouvé de bout en bout**
|
||
sur le **Tier 0** (`infra-pki-01` → `/etc/step-ca`, l'ancre de confiance) : sauvegarde **hors-nœud**
|
||
vers `backup-01`, puis **restauration byte-identique** des clés CA (`root_ca_key`,
|
||
`intermediate_ca_key`, `ca.json`). Piège corrigé : le plancher `/etc/hosts` d'un nœud existant
|
||
ignore un nœud nouvellement ajouté → rafraîchir le socle.
|
||
- **Sauvegardes Tier 1 généralisées — 5 nœuds, restauration prouvée.** `client_backup` étendu (jobs
|
||
déclaratifs en host_vars) à : `data-sql-01` (`pg_dumpall` — keycloak/forgejo/icingadb),
|
||
`id-ldap-01` (`slapcat` LDIF — les identités), `infra-mail-01` (`/var/vmail` — les boîtes),
|
||
`forge-01` (`/var/lib/forgejo` + `/etc/forgejo` — dépôts Git ; la BD est déjà couverte par PG).
|
||
**Prouvé par restauration** : dump PostgreSQL restauré contient bien les 3 bases (`CREATE DATABASE
|
||
forgejo/icingadb/keycloak`) ; LDIF restauré contient `testmail`. Les 5 dépôts restic (pki, sql,
|
||
ldap, mail, forge) sont hors-nœud sur `backup-01`, chiffrés. Ajouter un service à sauvegarder =
|
||
déclarer un job. Reste : cible **offsite** (3-2-1, Étape B — le dépôt n'est qu'une URL swappable).
|
||
- **Consolidation — binding `annuaire` (`resoudre_annuaire`) + retrait de la cruft.**
|
||
*Cruft* : supprimés les 8 dossiers-catégories inertes (`roles/{applications,backup,database,
|
||
identity,monitoring,proxmox,storage,web}/`, README seuls) et les 5 playbooks-échafaudages `debug`
|
||
sans rôle (nextcloud, collabora, client_supervision, web_frontal, web_dorsal) ; l'intention reste
|
||
documentée dans `docs/catalogue-services.md`. *Binding annuaire* : nouveau rôle utilitaire partagé
|
||
`resoudre_annuaire` (comme `resoudre_base`) qui **dérive** la connexion OpenLDAP du `domaine_interne`
|
||
+ un hôte d'annuaire surchargeable — LE seul endroit où le nom d'hôte de l'annuaire est fixé, au lieu
|
||
d'être répété. Facts `resoudre_annuaire_{uri,port,base_dn,users_dn,bind_dn,bind_password}` (secret
|
||
déréférencé, `no_log`). **Migrés + prouvés (config neutre, `changed=0`)** : `serveur_keycloak`
|
||
(fédération LDAP — testmail token HTTP 200), `serveur_dovecot` + `serveur_postfix` (flux courriel
|
||
Postfix→LDAP→LMTP→Dovecot **livré de bout en bout**). Piège appris : les *defaults* d'un rôle inclus
|
||
ne persistent pas hors de son exécution — publier via `set_fact`.
|
||
- **Binding annuaire complété — `icingaweb2` + `client_ldap` migrés vers `resoudre_annuaire`.** Fin des
|
||
2 loose ends : `serveur_icingaweb2` (connexion LDAP dormante en mode SSO) résout via
|
||
`resoudre_annuaire` (redéploiement `changed=0`, SSO intact) ; `client_ldap` (SSSD, dormant) ne pointe
|
||
plus sur un `idm-01` périmé. **Plus AUCUN rôle ne code en dur l'hôte d'annuaire** — un seul point de
|
||
vérité (`resoudre_annuaire`).
|
||
- **Rôle `serveur_oauth2_proxy` — passerelle SSO OIDC générique (Keycloak) + Icinga Web 2 au SSO.**
|
||
oauth2-proxy (v7.15.3, binaire GitHub) place Keycloak **devant** n'importe quelle app sans OIDC
|
||
natif : elle reçoit l'utilisateur authentifié via en-tête, en auth `external`. Rôle paramétrable
|
||
(client, secret voûte, redirect, upstream, cookie voûte) — **réutilisable** pour toute app
|
||
OIDC-less. Éprouvé sur `sup-01` **devant Icinga Web 2** : client Keycloak `icingaweb2`,
|
||
oauth2-proxy `:4180` (exposé par l'edge) → upstream nginx local `:8080` → icingaweb2
|
||
`backend = external` (REMOTE_USER depuis `X-Forwarded-Preferred-Username`). **Prouvé** (flux
|
||
authorization code headless) : `testmail` → oauth2-proxy → Keycloak → **icingaweb2 `/dashboard`,
|
||
connecté** (« Se connecter avec Chezlepro », comme Grafana/Forgejo). Ferme le gap LDAP-direct
|
||
d'icingaweb2. **Réglages appris** : `insecure_oidc_allow_unverified_email` (les users LDAP n'ont
|
||
pas `email_verified` ; IdP interne de confiance) ; en reverse-proxy oauth2-proxy passe
|
||
`X-Forwarded-*` (pas `X-Auth-Request-*`) ; **handler nginx en `restart` (pas `reload`)** car un
|
||
changement d'adresse d'écoute n'est pas pris par un reload gracieux.
|
||
- **Rôle `serveur_icingaweb2` — Icinga Web 2 (UI native) + module IcingaDB : éprouvé.** App PHP
|
||
(php8.4-fpm) servie par un **nginx local**, exposée par l'edge (`icinga.lab.chezlepro.internal`,
|
||
auto-dérivé : vhost + cert SAN + A PowerDNS + alias plancher). Config **par fichiers `.ini`**
|
||
(config/resources/authentication/roles + module `icingadb`), pas d'assistant de setup. Base
|
||
IcingaDB via `resoudre_base` (registre). **Auth LDAP direct** vers OpenLDAP (LDAPS, `client_pki`
|
||
sur `sup-01`) — icingaweb2 n'a pas d'OIDC natif ; SSO-par-proxy = raffinement futur. **Prouvé** :
|
||
`testmail` (LDAP) se connecte (`/dashboard`), et le **module IcingaDB affiche la supervision**
|
||
(hôte `icinga`). Déploiement `failed=0` (le rôle est bon ; les frictions étaient dans le
|
||
simulateur de login curl : contrôle de cookie `_checkCookie`, champs `uid`/`submit_login`,
|
||
valeur CSRF avant `name`).
|
||
- **Module BPM (Business Process) éprouvé + codifié — pile Icinga complète.** `serveur_icingaweb2`
|
||
installe + active `icingaweb2-module-businessprocess` (`serveur_icingaweb2_modules`), crée le
|
||
répertoire des processus (éditable via l'UI, groupe `icingaweb2`, setgid) et **sème des processus
|
||
métier en IaC** (`serveur_icingaweb2_bpm_processes`, nom → contenu `.conf`). Format des feuilles
|
||
`host;service` (éprouvé via les fixtures du module). **Prouvé** : un processus « Supervision
|
||
Chezlepro » (agrège load/procs/swap/ping4/ssh du host `icinga` en logique ET) **rend un état**
|
||
dans l'UI (`testmail` connecté), **avec le backend IcingaDB** (pas d'IDO). Rôle re-prouvé (reset
|
||
→ recrée le processus, idempotent). BPM n'est **pas remplaçable par Grafana** (roll-up d'impact
|
||
métier). Pile Icinga = **moteur + Web 2 + BPM**, complète.
|
||
- **Cœur Icinga éprouvé (supervision active).** `serveur_icinga` (cœur : `icinga2` + `icingadb` +
|
||
`icingadb-redis`) déployé sur `sup-01` (🔧→⭐), base `icingadb` PostgreSQL via le registre.
|
||
**Prouvé** : 3 services actifs, et le moteur **supervise** — IcingaDB peuplée (1 hôte, 12
|
||
services, résultats de checks persistés en base). Zéro bug de déploiement (rôle bien bâti).
|
||
**Icinga Web 2 + module BPM restent différés** (phases dédiées : UI native + vues d'impact
|
||
métier ; le BPM n'est pas remplaçable par Grafana). Confirme aussi le **DRY `resoudre_base`**
|
||
sur icinga (les 3 rôles consommateurs validés).
|
||
- **Forgejo branché au SSO OIDC (« Se connecter avec Chezlepro ») — 2e app SSO, prouvée.**
|
||
`serveur_forgejo` (10.0.0) éprouvé sur `forge-01` (🔧→⭐), adossé à PostgreSQL (base `forgejo`
|
||
auto-provisionnée depuis le registre), exposé par l'edge (vhost + cert SAN + A PowerDNS + alias
|
||
plancher, tout auto-dérivé de `expose`). **Source OAuth2** vers Keycloak
|
||
(`forgejo admin auth add-oauth`, idempotent, realm `chezlepro`), client OIDC `forgejo`
|
||
enregistré via `serveur_keycloak_clients`. Auto-enregistrement OIDC (`[oauth2_client]
|
||
ENABLE_AUTO_REGISTRATION` + `ALLOW_ONLY_EXTERNAL_REGISTRATION` : identités depuis l'annuaire
|
||
seulement). **Prouvé** (flux authorization code headless) : `testmail` (LDAP) se connecte,
|
||
**compte auto-créé** (`testmail@lab.chezlepro.internal`), atterrit sur le tableau de bord.
|
||
**5 bugs de 1er déploiement corrigés** : dépendance périmée `serveur_sendmail`→`serveur_postfix`
|
||
(le vrai MTA) ; `app.ini` doit appartenir au user `git` (Forgejo persiste des secrets générés) ;
|
||
ordre admin/migrations (`flush_handlers` + `wait_for` avant `admin user create`) ; `HTTP_ADDR`
|
||
`127.0.0.1`→`0.0.0.0` (l'edge nginx est sur un autre hôte, 502 sinon) ; auto-enregistrement OIDC.
|
||
- **DRY : rôle utilitaire partagé `resoudre_base` (résolution BD depuis le registre).** Le bloc
|
||
copié-collé dans `serveur_keycloak`, `serveur_forgejo` et `serveur_icinga` (charger le registre,
|
||
filtrer par consommateur, déréférencer le secret via `lookup('vars', ...)`, résoudre hôte/port)
|
||
est extrait dans `roles/resoudre_base` (facts `resoudre_base_entree/db_password/db_host/db_port`,
|
||
`no_log`). Les 3 rôles l'incluent (`include_role`) et adoptent les facts. Le secret **ne quitte
|
||
toujours pas le rôle** (déréférencé au déploiement). **Fait « sur la preuve »** : re-déploiement
|
||
keycloak + forgejo `failed=0`, idempotent, `testmail` token Keycloak HTTP 200. Ferme le reste
|
||
noté de la Phase 2 des bindings (cf. `docs/bindings-conception.md`).
|
||
- **PowerDNS — A d'exposition auto-dérivés (le DNS de la Phase 3 des bindings).** `serveur_powerdns`
|
||
génère désormais, dans la zone interne, un enregistrement A pour chaque FQDN d'exposition (champ
|
||
`expose` des applications) vers l'**edge qui le sert** (`domaines.edge`) — via
|
||
`expositions_des_applications` (même source que les vhosts nginx et les SANs du cert edge).
|
||
Déclarer `expose` produit maintenant **vhost + SAN de cert + enregistrement DNS**, tout dérivé.
|
||
**Prouvé** : `dig @infra-dns-01 grafana.lab.chezlepro.internal` et `keycloak.…` → `192.168.15.21`
|
||
(edge). Option `serveur_powerdns_publier_expositions` (défaut true). **Limite / reste** : PowerDNS
|
||
est **autoritatif, pas récursif** — pour que les nœuds *utilisent* ces A sans casser la résolution
|
||
Internet, il faut un **récursif** (pdns-recursor : forward de la zone interne + récursion du reste)
|
||
ou garder le plancher `/etc/hosts`. Ne PAS repointer naïvement `client_dns` vers l'autoritatif.
|
||
- **`hosts_statiques` — alias d'exposition dans le plancher `/etc/hosts` (résolution client, sûre).**
|
||
Le plancher pose désormais, sur **chaque nœud**, `<IP edge> <FQDN exposé>` pour chaque `expose`
|
||
(dérivé de `domaines.edge`, même source que nginx/PowerDNS). Indépendant du DNS, aucun risque de
|
||
couper la résolution (choix retenu vs pdns-recursor). Chargement du plan **best-effort**
|
||
(`stat` **`delegate_to: localhost`** + `become: false` — les registres vivent sur le nœud de
|
||
contrôle ; ignoré si le plan est absent, ex. préparation du template). **Prouvé** : `/etc/hosts`
|
||
d'obs-01 régénéré avec `keycloak`/`grafana` → edge (ligne manuelle éliminée), `getent` OK, et le
|
||
**flux SSO Grafana fonctionne via la résolution du plancher** (`login: testmail`). Boucle Phase 3
|
||
fermée : déclarer `expose` → **vhost + SAN cert + A PowerDNS + alias plancher**, tout dérivé.
|
||
Bugs corrigés en chemin : `serveur_loki` (groupe `loki` manquant), `stat` sur cible→contrôle,
|
||
`become` inutile sur le contrôle.
|
||
- **Rôle `client_unbound` — résolveur local (DNS dynamique) : éprouvé sur un nœud.** Unbound par
|
||
nœud (`127.0.0.1`) avec **stub-zone** vers l'autoritatif interne (PowerDNS) + **récursion**
|
||
Internet (ou forward via `client_unbound_transitaires`). Alternative *dynamique* au plancher
|
||
`/etc/hosts` statique, sans casser Internet. Bascule de `/etc/resolv.conf` **protégée**
|
||
(`client_unbound_apply` + `client_unbound_confirm`) **et validée AVANT** (Unbound doit résoudre
|
||
interne + Internet, sinon pas de bascule → nœud jamais coupé). **Prouvé sur data-sql-01** (rayon
|
||
d'impact minimal) : `dig @127.0.0.1 keycloak/id-sso-01.lab.chezlepro.internal` → PowerDNS,
|
||
`deb.debian.org` → récursion, `apt` OK. Rôle sûr par défaut (`apply: false` : installe Unbound
|
||
sans toucher au resolver). Rollout flotte = opt-in par nœud. **Note direction** : OPNsense
|
||
embarque Unbound → à terme, l'Unbound *réseau* peut vivre sur l'appliance de bordure (nœud
|
||
public, Étape B) ; le rôle par-nœud reste portable et complémentaire (cache local).
|
||
- **Bindings — Phase 1 : résolveur de liens dans `instancier.py` (relations service→service
|
||
déclaratives).** Une application déclare ses `liens: [{vers, role}]` dans
|
||
`plan/applications.yml` ; chaque rôle décrit les liens qu'il accepte dans `meta/liens.yml`
|
||
(`setops_liens.accepte`, comme `meta/empreinte.yml`). `instancier` résout la cible (FQDN interne
|
||
**dérivé de la nomenclature** + `domaine_interne`), substitue les gabarits (`{cible.fqdn}`,
|
||
`{cible.hote}`, `{cible.ip}`) et **injecte les variables en host_vars du consommateur**.
|
||
Validation : rôle accepteur, cible existante, genre attendu. **Migration prouvée** : les liens
|
||
mail Postfix→Dovecot (`mailstore`) et Postfix→rspamd (`milter`) passent de group_vars codés en
|
||
dur à des liens déclaratifs — `make instancier` donne **DIFF VIDE** (mêmes variables générées),
|
||
puis les group_vars sont retirés. La topologie mail devient déclarative et portable.
|
||
Cf. `docs/bindings-conception.md`. Suite : bases (Phase 2), exposition/domaines (Phase 3), GUI (Phase 4).
|
||
- **Bindings — Phase 2 (bases) : constat + réconciliation de la note (`docs/bindings-conception.md` §5/§9).**
|
||
Inspection du code réel : le binding app→base **existe déjà** — **côté base** (`consommateur`/`portee`
|
||
dans `bases-donnees.yml`), résolu **dans le rôle** au déploiement (`include_vars` + filtre +
|
||
`lookup('vars', secret)`), sur 4 rôles (postgresql, forgejo, keycloak, icinga). **Délibérément
|
||
conservé** (le secret ne quitte jamais le rôle) — ne PAS dupliquer en app-side/instancier. Deux
|
||
directions assumées : app→app côté app (instancier), app→base côté base (registre). Reste (reporté
|
||
à l'épreuve de Keycloak) : factoriser le bloc de résolution copié-collé en include partagé (DRY).
|
||
- **`docs/carte-set-ops.md` — carte d'orientation (index + mécanismes transverses).** Après audit
|
||
du dépôt : point d'entrée « à lire d'abord » (index du corpus, ~22 docs), et **catalogue des
|
||
mécanismes** dispersés dans le code (les 2 directions de binding, pont de cert, résolution BD
|
||
par registre, socle-first, sûreté check-mode, voûte, dimensionnement) avec **où ils vivent**.
|
||
But : ne plus re-découvrir l'existant. Constat : la **cruft était déjà inventoriée** dans
|
||
`catalogue-services.md` (rôles-catégories inertes, échafaudages) — non dupliquée, référencée.
|
||
`catalogue-services.md` « État d'implémentation » **rafraîchi** (rôles éprouvés sur VM réelles :
|
||
socle, PKI, LDAP, DNS, nginx, pile courriel). Pointeur ajouté depuis `architecture-set-ops.md`.
|
||
- **`serveur_postgresql` et `serveur_keycloak` éprouvés sur VM réelles (🔧→⭐).** PostgreSQL
|
||
déployé (data-sql-01), écoute réseau + pg_hba VLAN, et **provisionne la base `keycloak` depuis
|
||
le registre** (`bases-donnees.yml`) — **binding app→base prouvé en réel** (base + rôle créés,
|
||
mot de passe = `vault_bd_keycloak`). Keycloak 26.0.7 déployé (id-sso-01), **mode prod**,
|
||
connecté à PostgreSQL (**87 tables** du realm master écrites), **token admin obtenu** (auth
|
||
adossée à la BD). Lacunes connues (documentées `catalogue-services.md`) : fédération LDAP et
|
||
edge nginx pas encore câblés. Reste : DRY du bloc de résolution BD (keycloak/forgejo/icinga).
|
||
- **`serveur_keycloak` — fédération LDAP (modèle d'identité A) : automatisée et prouvée.** Le rôle
|
||
configure, via `kcadm` (idempotent), un realm applicatif (`serveur_keycloak_realm`, déf.
|
||
`chezlepro`) et un **provider de stockage LDAP READ_ONLY** vers OpenLDAP (LDAPS, `uid`/`entryUUID`,
|
||
`inetOrgPerson`). TLS LDAPS validé via le **truststore système** (`truststore-paths` →
|
||
`/etc/ssl/certs/ca-certificates.crt`, racine step_ca posée par **client_pki**, désormais requis
|
||
sur le nœud). Secrets par `environment` + `no_log`. **Éprouvé avant codification** puis prouvé
|
||
par le rôle : un utilisateur LDAP (`testmail`) obtient un token via le realm (HTTP 200), et le
|
||
redéploiement est **idempotent** (`changed=0`). Nouveau : `tasks/federation-ldap.yml`. Reste :
|
||
edge nginx (accès HTTPS par nom), mappers d'attributs/groupes fins.
|
||
- **Edge nginx + exposition (Phase 3 des bindings) — prouvés avec Keycloak.** Sans changement de
|
||
code : la machinerie `serveur_nginx_publier_expositions` **existait déjà** (lit `expose` des
|
||
applications + `edge` de `domaines.yml`, dérive `amont = http://<IP hôte>:<port>`, génère le
|
||
vhost avec `X-Forwarded-*`). Le bac à sable déclare `keycloak.expose:
|
||
[keycloak.lab.chezlepro.internal]` + le domaine interne `lab.chezlepro.internal` (edge
|
||
`serveur_nginx`). **Prouvé** : le vhost s'auto-génère (`keycloak.lab.chezlepro.internal →
|
||
http://192.168.15.81:8080`), et la découverte OIDC via l'edge renvoie
|
||
`"issuer":"https://keycloak.lab.chezlepro.internal/..."` (les `X-Forwarded` passent, Keycloak
|
||
se sait derrière HTTPS). **Limite connue** : le cert TLS de l'edge est encore le snakeoil
|
||
auto-signé (avertissement navigateur). **Raffinement recommandé** (réutilise l'existant, pas de
|
||
nouveau mécanisme) : ajouter les FQDN d'exposition aux `client_pki_sans` de l'edge (client_pki
|
||
demande + renouvelle déjà le cert d'hôte), puis pointer `serveur_nginx_certificat` sur le cert
|
||
client_pki (`/etc/step/certs/<edge>.crt`).
|
||
- **Cert de l'edge : snakeoil → step_ca (HTTPS valide).** Appliqué le raffinement ci-dessus :
|
||
`client_pki` ajouté à l'edge, ses `client_pki_sans` incluent le FQDN d'exposition
|
||
(`keycloak.lab.chezlepro.internal`), et `serveur_nginx_certificat`/`_cle` pointent sur le cert
|
||
client_pki. **Prouvé** : HTTPS `HTTP 200` avec `ssl_verify_result=0` (chaîne validée contre la
|
||
racine step_ca, nom correct), émetteur `Set-OPS Internal CA`. Sans nouveau code (client_pki +
|
||
group_var). **Gaps notés** : (1) recharger nginx au **renouvellement** du cert (le cert-renewer
|
||
renouvelle en place, nginx ne recharge pas seul — hook à ajouter) ; (2) **auto-dériver** les SANs
|
||
d'exposition de l'edge depuis le plan (au lieu de les lister dans le group_var).
|
||
- **Grafana branché au SSO OIDC (« Se connecter avec Chezlepro ») — prouvé de bout en bout.**
|
||
`serveur_grafana` : config OIDC via `GF_AUTH_GENERIC_OAUTH_*` (client confidentiel `grafana`,
|
||
realm `chezlepro`, secret `vault_grafana_oidc`). `serveur_loki` + `serveur_prometheus` +
|
||
`serveur_grafana` déployés sur `obs-01` (🔧→⭐). **Bug de rôle corrigé** : `serveur_loki` créait
|
||
le répertoire en `group: loki` alors que le paquet crée l'utilisateur en `nogroup` sans groupe
|
||
`loki` → ajout de la création du groupe. **Prouvé (flux authorization code headless, via l'edge
|
||
HTTPS)** : `testmail` (user LDAP) se connecte à Grafana par le SSO — `/api/user` renvoie
|
||
`login: testmail`, email et nom **fédérés depuis LDAP**. Chaîne complète LDAP → Keycloak →
|
||
Grafana. **Gaps notés (pour rendre 100 % déclaratif)** : (1) l'**enregistrement du client OIDC**
|
||
dans Keycloak a été fait via `kcadm` **à la main** (à codifier — rôle grafana ou liste de clients
|
||
côté keycloak) ; (2) la **résolution** `keycloak.…internal → edge` sur obs-01 est un `/etc/hosts`
|
||
manuel (**PowerDNS devrait porter les A d'exposition** — chaînon récurrent) ; (3) mapping de
|
||
rôles Grafana (tous Viewer par défaut).
|
||
- **`serveur_keycloak` — enregistrement des clients OIDC codifié (gap précédent fermé).** Le rôle
|
||
gère une **liste déclarative** `serveur_keycloak_clients` (`clientId`, `redirect_uris`,
|
||
`web_origins`, `secret`) et enregistre chaque client confidentiel via **kcadm idempotent**
|
||
(`tasks/clients-oidc.yml`, create-si-absent, `no_log`). Décision : **côté Keycloak** (les creds
|
||
admin restent dans le seul rôle Keycloak, pas répandus dans chaque rôle app) ; le `secret`
|
||
référence la même variable de voûte que l'app. **Prouvé** : client `grafana` supprimé → rôle →
|
||
recréé → `testmail` se connecte à Grafana (`login: testmail`) ; redéploiement **idempotent**
|
||
(`changed=0`). Le déploiement de Grafana au SSO est désormais **autonome**.
|
||
|
||
## 2026-07-02
|
||
|
||
### Décidé
|
||
- **Bindings — conception des relations app/base/serveur/domaine (`docs/bindings-conception.md`).**
|
||
Les relations service→service sont aujourd'hui codées en dur, éparpillées dans des group_vars
|
||
(ex. Postfix→Dovecot/rspamd/LDAP), ce qui casse la portabilité multi-tenant. Direction retenue :
|
||
**liens déclarés côté application** (`liens: [{vers, role}]`), résolus par `instancier.py` en
|
||
variables Ansible ; chaque rôle décrit les liens qu'il accepte dans `meta/liens.yml` (comme
|
||
`meta/empreinte.yml`) ; FQDN cible **dérivé de la nomenclature** (jamais codé en dur). Domaines
|
||
publics traités comme lien `exposition` (écrit sur l'edge). Réconcilie l'existant (bases
|
||
`consommateur`, `domaines.edge`). Preuve de migration ciblée : les 3 liens mail. Implémentation
|
||
à suivre (phasée).
|
||
- **Licence : passage de CC BY-NC-SA 4.0 à AGPLv3.** Les licences Creative Commons ne sont pas
|
||
faites pour du logiciel (position de CC elle-même) et la clause **NonCommercial contredisait
|
||
le principe fondateur « tout est libre »** — en plus de bloquer les artisans/coopératives
|
||
visés. `LICENSE` remplacé par le **texte officiel intégral de l'AGPLv3** (verbatim, non
|
||
modifié). Attribution + modèle **libre + services/certification** documentés dans le `README`
|
||
(méthode d'attribution recommandée, sans toucher au texte de la licence). L'AGPLv3 protège la
|
||
souveraineté (anti-captation propriétaire en SaaS) **sans interdire l'usage commercial**.
|
||
- **Architecture d'identité/SSO (`docs/identite-sso.md`).** Modèle A : **OpenLDAP source de
|
||
vérité**, **Keycloak fédéré** (SSO web OIDC, MFA, self-service), **mail en bind LDAP
|
||
direct**. Une identité, un mot de passe, deux chemins d'auth (web→Keycloak, mail→LDAP),
|
||
tout adossé au même OpenLDAP. Sert la portabilité multi-tenant (chaque tenant = son LDAP +
|
||
son Keycloak fédéré). Pas Keycloak-source (casse-tête mail + moins portable).
|
||
- **Service courriel : pivot de Stalwart vers Postfix + Dovecot + rspamd.** La Phase 1
|
||
Stalwart (`serveur_stalwart`) avait été prototypée et déployée (v0.16.11, install +
|
||
démarrage en mode récupération). Le prototypage a révélé un projet **trop jeune/volatil
|
||
pour un pilier mail critique** : config cassée entre 0.15 et 0.16, outil IaC
|
||
`stalwart config apply` **annoncé mais non livré** dans le binaire, API REST supprimée
|
||
(JMAP), gros backlog. Pivot vers la stack **mature Postfix/Dovecot/rspamd**, en prime
|
||
**100 % configurable par fichiers** (alignée au modèle déclaratif Set-OPS). Le rôle
|
||
`serveur_stalwart` est **retiré** (git en garde la trace) ; `docs/courriel-conception.md`
|
||
mis à jour. Réévaluer Stalwart ~2028.
|
||
|
||
### Ajouté
|
||
- **`docs/pouvoirs-set-ops.md` — bilan des capacités du moteur.** Inventaire structuré
|
||
(moteur/plan, plan de contrôle GUI, socle durci, piliers d'infrastructure, services outillés,
|
||
patrons d'ingénierie), distinguant honnêtement « prouvé sur cluster réel » de « outillé ».
|
||
- **Rôle `serveur_rspamd` (rspamd 3.x) — antispam + DKIM, en milter sur Postfix.** Installé
|
||
sur le nœud edge-mta (avec Postfix), backend **Redis** local, worker proxy en **mode milter
|
||
auto-scan** (`:11332`), **signature DKIM** sortante (clé générée par le rôle de façon
|
||
idempotente ; enregistrement DNS public affiché pour l'Étape B). Config par surcharges
|
||
`/etc/rspamd/local.d/`. Postfix branché via `smtpd_milters` (option `serveur_postfix_rspamd_milter`,
|
||
`milter_default_action = accept` → tolérant si rspamd indisponible). **Prouvé** : un courriel
|
||
traversant le milter ressort **scanné** (`rspamc stat` : 1) et **signé DKIM** (`DKIM-Signature:
|
||
d=…`), puis livré et lu en IMAP. **Étape A (courriel interne) complète** : dovecot + postfix + rspamd.
|
||
- **Flux courriel interne PROUVÉ de bout en bout (Étape A).** Envoi → Postfix (`edge-mta`,
|
||
validation LDAP) → LMTP réseau → Dovecot (mail-store) → boîte Maildir → **lu en IMAP**
|
||
(auth LDAP, TLS step_ca) : `status=sent`, message lu (sujet + corps). Réglages Dovecot 2.4
|
||
qui débloquent la remise LMTP : `userdb static { static_allow_all_users = yes }` (sinon
|
||
NOTFOUND pour l'expéditeur/raw-mail-user externe), `mail_inbox_path =` vidé (le défaut mbox
|
||
`/var/mail` root refusait l'autocréation de l'INBOX), Maildir explicite (`mail_home` +
|
||
`mail_path = %{home}/Maildir`), chemin par nom d'utilisateur (home identique côté LMTP
|
||
local-part et IMAP adresse complète ; mono-domaine, multi-domaine = raffinement Étape B).
|
||
- **Rôle `serveur_postfix` (Postfix 3.x) — MTA du nœud edge-mta.** Réception `:25`, cartes
|
||
**LDAP** (validation des boîtes via l'attribut `mail`), remise **LMTP réseau** vers le nœud
|
||
mail-store Dovecot (`virtual_transport = lmtp:inet:[…]:24`), TLS via **step_ca** (pont de
|
||
cert), aucune boîte locale. Config `main.cf` + carte `ldap-mailboxes.cf`, validée par
|
||
`postfix check`. Secret de bind : `vault_openldap_admin`. Nécessite `serveur_postfix_mailstore_hote`
|
||
(FQDN du mail-store). Validé statiquement ; déploiement réel à suivre.
|
||
- **Rôle `serveur_dovecot` (Dovecot 2.4) — déployé et prouvé.** IMAP `:993`/`:143` +
|
||
LMTP, **auth/annuaire LDAP** (vers `serveur_openldap`, filtre `mail`), stockage Maildir
|
||
(user système `vmail`), **TLS via step_ca** (pont de cert + resync au renouvellement),
|
||
neutralisation de l'auth système par défaut. Config en drop-in **syntaxe Dovecot 2.4**
|
||
(`mail_driver`, `ssl_server_cert_file`, `passdb ldap`/`userdb static`, `%{user}`),
|
||
**validée par `doveconf`** au déploiement. Sockets d'intégration Postfix **conditionnels**
|
||
(rendus si l'utilisateur `postfix` est co-localisé). Prouvé : `doveadm auth test` — bon
|
||
mot de passe accepté, mauvais refusé, sur cert step_ca. Secret de bind : `vault_openldap_admin`.
|
||
- **`serveur_openldap` durci pour la prod : TLS via step_ca + organisation en intrant.**
|
||
- **TLS (LDAPS + STARTTLS)** : le certificat d'hôte step_ca (déposé par `client_pki`,
|
||
`root:root 600`) est synchronisé vers un emplacement lisible par `openldap` (`/etc/ldap/tls`)
|
||
par un script + une unité `path` systemd qui **re-synchronise et recharge slapd à chaque
|
||
renouvellement** ; `olcTLS*` configuré dans `cn=config`, `SLAPD_SERVICES` expose `ldaps://`.
|
||
Dégrade proprement (slapd en clair local) si `client_pki` n'a pas encore posé le cert.
|
||
- **Organisation** : nouvel intrant `chezlepro_organisation` (remplace le « Exemple Inc » codé).
|
||
- *Écrit en code de prod, éprouvé statiquement ; validation par déploiement réel à suivre.*
|
||
- **`docs/courriel-conception.md`** — cadrage du futur service de courriel souverain :
|
||
full self-host, suite **Stalwart** (adoptée, enveloppée par un rôle mince), topologie
|
||
MX primaire + MX secours, intégration identité (LDAP) / DNS / PKI (Let's Encrypt public
|
||
vs step_ca interne), enregistrements DNS publics, décisions ouvertes (IP/PTR, secours,
|
||
stockage) et phasage. **Conception seulement — aucun rôle livré.**
|
||
|
||
## 2026-07-01
|
||
|
||
### Ajouté
|
||
- **État RÉEL vs plan dans le GUI (sonde de vie + auto-actif).** Le badge `planifié`/
|
||
`actif` décrit l'*intention* du plan, pas l'existence de la VM — d'où la confusion
|
||
« serveur planifié mais vivant ». Deux ajouts :
|
||
- **Sonde de vie** : le GUI teste la joignabilité SSH de chaque hôte en arrière-plan
|
||
(`/api/sondes`, en parallèle) et affiche un état réel — **● vivante** / **● injoignable**
|
||
— sur les tuiles et dans le détail (« Plan : … · Réel : … »), distinct du plan.
|
||
- **Auto-actif** : un hôte qu'on **matérialise** (clone `creer` réussi) ou qu'on
|
||
**déploie** passe automatiquement `actif` dans le plan (matérialisé = actif), puis
|
||
l'inventaire est **régénéré** pour que le changement se voie partout (en-tête inclus).
|
||
- **Compteur « vivantes »** dans l'en-tête (depuis la sonde), à côté de actifs/planifiés.
|
||
|
||
### Corrigé
|
||
- **nginx ne validait pas sur Debian 13 (`server_tokens` en double).** Debian 13 livre
|
||
`server_tokens off;` **actif** dans `/etc/nginx/nginx.conf` (avant : commenté). Le
|
||
drop-in `conf.d/99-setops.conf` du rôle le redéclarait → `nginx -t` échouait
|
||
(« directive is duplicate ») et le déploiement plantait au handler de validation. Le
|
||
rôle `serveur_nginx` neutralise désormais la ligne distro (le drop-in reste l'unique
|
||
source). Trouvé en déployant nginx pour de vrai sur un hôte edge.
|
||
- **Une voûte chiffrée cassait `instancier` / « Appliquer le plan ».** `ansible-inventory
|
||
--list` (utilisé pour la comparaison sémantique du plan) tente de déchiffrer
|
||
`group_vars/all/vault.yml` et échoue sans mot de passe (`exit 4`) — alors que
|
||
l'opération est structurelle, sans secret. `instancier` utilise désormais
|
||
automatiquement le fichier conventionnel `~/.config/setops-vault-pass` (si
|
||
`ANSIBLE_VAULT_PASSWORD_FILE` n'est pas déjà défini).
|
||
- **Secrets des rôles non câblés à la voûte (échafaudage manquant).** Les rôles à
|
||
secrets déclaraient `serveur_X_password: ""` avec, en commentaire seulement, la
|
||
variable de voûte attendue (`{{ vault_X }}`) — sans mapping réel. Résultat : remplir
|
||
la voûte selon `vault.exemple.yml` ne suffisait pas, le secret restait vide et
|
||
l'assertion « secrets requis » échouait. Les 12 secrets des 8 rôles (`serveur_step_ca`,
|
||
`serveur_forgejo`, `serveur_grafana`, `serveur_keycloak`, `serveur_openldap`,
|
||
`serveur_redis`, `client_ldap`, `client_pki`) pointent désormais vers leur variable
|
||
de voûte : `serveur_X_password: "{{ vault_X | default('') }}"`. Chaque instance n'a
|
||
plus qu'à remplir ses `vault_*` dans sa voûte chiffrée ; aucun mapping par instance.
|
||
- **« Vérifier » (dry-run `--check`) échouait faussement sur un hôte frais.** Les tâches
|
||
« démarrer service » et les handlers « redémarrer / recharger / valider » des rôles
|
||
applicatifs touchent un paquet que `--check` n'installe pas réellement → le service
|
||
(ou le fichier de zone/conf) n'existe pas encore → faux `fatal`, qui **bloquait le
|
||
déploiement** (le dry-run doit réussir pour débloquer « Déployer »). Ajout de
|
||
`when: not ansible_check_mode` sur ces tâches et handlers des 13 rôles `serveur_*`
|
||
(29 gardes). En dry-run elles sont sautées ; en vrai déploiement, inchangées.
|
||
- **Le bouton ⚙ « Appliquer le plan » du GUI refusait en silence** dès que le plan
|
||
divergeait de l'inventaire (il appelait `instancier appliquer` **sans** `--force`).
|
||
Résultat : après une édition (disque, état, auto-actif), l'inventaire n'était jamais
|
||
régénéré et les compteurs restaient figés. Le clic « Appliquer » **est** l'intention
|
||
explicite → le GUI force désormais (git reste le filet).
|
||
|
||
- **Bouton « Pousser » dans le GUI — le flux devient 100 % cliquable.** Chaque objet
|
||
du détail se matérialise sur son hôte, en streaming console (avec mot de passe vault
|
||
+ confirmation renforcée en prod) :
|
||
- **Serveur** → `🖥 Pousser` clone la VM depuis le golden template (`make creer-vm`),
|
||
disponible même sur un hôte planifié — comble le trou : le GUI ne clonait pas.
|
||
- **Application** → `Pousser` déploie l'hôte porteur (`make deployer HOTE=<hôte>`).
|
||
- **Base** → `Pousser` déploie l'hôte du serveur de BD (crée la base).
|
||
Nouveaux modes `creer` / `pousser` dans `executer_flux` + routes `/api/creer` et
|
||
`/api/pousser`. Flux complet depuis l'interface : éditer → ⚙ Appliquer → 🖥 Pousser
|
||
→ Vérifier → Déployer.
|
||
- **Premier déploiement RÉEL validé de bout en bout** (cluster asgard) : flux canonique
|
||
plan-piloté clonant + durcissant + faisant tourner un PowerDNS qui résout. Voir les 3
|
||
correctifs ci-dessous.
|
||
|
||
## 2026-06-30
|
||
|
||
### Ajouté
|
||
- **Plancher de résolution `/etc/hosts` (indépendant du DNS).** Nouveau rôle de socle
|
||
`hosts_statiques` (dans `serveur_debian`) : génère `/etc/hosts` sur **chaque** VM
|
||
depuis l'inventaire (nom + FQDN interne → IP réelle). Tout l'écosystème se résout
|
||
par nom **même serveur DNS éteint**, et le bootstrap ne dépend plus du DNS. PowerDNS
|
||
devient une **commodité** (zone/externe/dynamique) ; `client_dns` est rendu tolérant
|
||
(inerte si aucun DNS interne) et sa dépendance à `serveur_powerdns` passe **molle**.
|
||
- **Adressage fédéré : index d'instance.** Le VMID n'est plus codé `9CSNN` en dur :
|
||
il prend le préfixe d'un `index` déclaré en tête de `plan/nomenclature.yml`
|
||
(`{index}{catégorie}{service}{séq}`). Convention : `supernet = 10.(10+index).0.0/16`,
|
||
`VMID = index·CSNN`. Permet à N écosystèmes de **coexister/s'interconnecter** sans
|
||
collision (Chezlepro=1 → `10.11`/`1xxxx`, Technolibre=2 → `10.12`/`2xxxx`). Sans
|
||
index → `9CSNN` (rétro-compatible ; bacs à sable, plages ad-hoc `172.19.x`).
|
||
Code mort retiré (`deriveServeur` JS). Voir `docs/multi-instances.md`.
|
||
- **Doc `docs/multi-instances.md`** : cadrage « un moteur, N écosystèmes » — l'instance
|
||
comme dépôt autonome, la bascule, l'isolation, et le socle multi-tenant.
|
||
- **Bascule d'instance (`make instance-utiliser NOM=…`).** Repointe le symlink
|
||
`instance` vers un autre dépôt d'instance (prod ↔ bac à sable) ;
|
||
`make instance-courante` affiche l'instance montée. Permet d'exploiter plusieurs
|
||
instances (séparation **par instance**) depuis un seul moteur.
|
||
- **Identité des intrants relative à l'inventaire.** Le panneau « Intrants » lit/écrit
|
||
l'identité dans `group_vars/all/10-intrants.yml` de l'inventaire monté (fichier réel
|
||
pour une instance autonome, symlink vers une source partagée sinon — l'écriture suit
|
||
le symlink). Fonctionne donc aussi bien pour une instance « par instance » que pour
|
||
l'ancien partage lab/production.
|
||
- **Inventaire d'instance neutre et configurable (`principal` / `SETOPS_INVENTAIRE`).**
|
||
Le moteur (Makefile + `inventory_gui`, `instancier`, `config_proxmox`, `serveurs`,
|
||
`applications`) ne code plus en dur `inventories/lab` / `inventories/production` :
|
||
il vise **un inventaire par instance**, détecté de façon **rétro-compatible**
|
||
(`principal` > `production` > `lab`) et surchargeable par `SETOPS_INVENTAIRE`. Les
|
||
instances existantes (découpage lab/production) continuent de fonctionner à
|
||
l'identique ; les nouvelles peuvent adopter `inventories/principal/`. Deuxième pierre
|
||
de la séparation **par instance** (la 1re étant le drapeau `setops_production`).
|
||
- **Garde-fou de prudence par instance (`setops_production`).** Le déploiement réel
|
||
est désormais possible sur **toute instance** (un bac à sable déploie sur *son*
|
||
infra lab — il est isolé **et** fonctionnel). Le drapeau `setops_production` dans
|
||
`group_vars/all/` ne **bloque** plus rien : il marque la PRODUCTION pour exiger une
|
||
**confirmation renforcée** au déploiement (bannière/badge rouge « PROD », bouton
|
||
Déployer en rouge, re-saisie du nom d'hôte) ; un bac à sable (`false`) affiche
|
||
« bac à sable » et déploie sans cette étape. Rétro-compatible (à défaut de drapeau,
|
||
ancien repère « inventaire production »). Le GUI affiche toujours quel type
|
||
d'instance est monté.
|
||
|
||
### Modifié
|
||
- **Voûte de secrets unique par environnement.** Fini les voûtes éparpillées : tous
|
||
les secrets de l'instance (token Proxmox + 17 `vault_*` pour PKI, LDAP/SSO, bases,
|
||
forge, observabilité) vivent dans **un seul fichier chiffré**,
|
||
`inventories/<env>/group_vars/all/vault.yml`. Gabarit committé
|
||
`exemples/vault.exemple.yml`. `make config` (`config_proxmox.py`) écrit/édite
|
||
désormais cette voûte (semée depuis le gabarit si absente). `.gitignore` durci
|
||
(`**/vault.yml`). Docs mises à jour (config-proxmox.md avec étapes de migration,
|
||
intrants-communs.md §H, QUICKSTART). Le GUI ne stocke toujours aucun secret.
|
||
|
||
### Corrigé
|
||
- **Trois bugs trouvés au premier déploiement réel (cluster asgard).**
|
||
- **Clonage/Makefile codaient `inventories/lab` en dur** (config Proxmox + voûte) —
|
||
vestige du modèle env qui cassait les instances `principal`. Le playbook de clonage
|
||
et le Makefile détectent maintenant l'inventaire (`lab` > `principal` > `production`).
|
||
- **Redimensionnement disque non idempotent** : quand le disque dérivé du plan est
|
||
plus petit que le golden template, Proxmox refuse (`shrinking disks is not
|
||
supported`) et le clone échouait. Le resize est désormais **grow-only** (tolère le
|
||
cas, la VM garde le disque du template — le dérivé est un minimum).
|
||
- **PowerDNS refusait de démarrer** (`multiple backends 'bind'`) : le rôle
|
||
redéclarait `launch+=bind` que le paquet `pdns-backend-bind` pose déjà. Le rôle ne
|
||
déclare plus `launch` (seulement `bind-config`).
|
||
- **`chezlepro_timezone` n'était appliqué nulle part.** Cet intrant de base global
|
||
était défini mais aucun rôle ne s'en servait. Le rôle `chrony` (appliqué à tout
|
||
hôte via `serveur_debian`) règle désormais le fuseau horaire à partir de
|
||
`chezlepro_timezone` (`chrony_timezone` par défaut, vide = ne pas toucher). Les
|
||
autres défauts globaux (nœud / stockage / pont Proxmox) étaient déjà réutilisés
|
||
comme valeurs par défaut, surchargeables par hôte.
|
||
|
||
### Modifié
|
||
- **Détail GUI : section « Groupes (dérivés) » retirée (redondante).** Depuis la fusion
|
||
en atelier maître-détail, elle ne répétait que le socle (universel), les rôles des
|
||
applications (déjà dans « Applications ici ») et les intégrations (déjà cochées). Son
|
||
seul signal unique — le prérequis bloquant — est désormais nommé dans le pied
|
||
(« Prérequis manquant : … ») ; le panneau Dépendances reste pour la vue d'ensemble.
|
||
Code mort retiré (`renduGroupe`, `detailGroupe`, `titreGroupe`, CSS `.groupe*`).
|
||
- **Nettoyage CSS/HTML du GUI** après la refonte : retrait des règles et éléments
|
||
morts (`.message`, `.chips-filtre`/`.chip-f`, `.onglet*`, `.base-ligne`,
|
||
`.bases-liste`, `.ch-grp*`/`.ch-fleche`/`.ch-roles`, `.champ-val`, divs `#message`
|
||
et `#chips`).
|
||
- **GUI refondu en atelier maître-détail unifié.** Toutes les vues suivent le même
|
||
motif : tuiles à gauche, **détail + saisie à droite**, le panneau droit reflétant
|
||
la sélection de la vue courante (fin du panneau « figé » au changement de vue).
|
||
Les vues **Applications** et **Bases** passent de tableaux pleine largeur à ce
|
||
motif ; la saisie se fait dans le panneau droit, avec **liens cliquables** entre
|
||
objets (serveur → application → base). Le panneau droit est élargi (~38 %).
|
||
- **Fusion Serveur/Hôte.** Les vues « Inventaire » (hôtes, lecture seule) et
|
||
« Serveurs » (plan) faisaient doublon : elles sont fusionnées en **une seule vue
|
||
Serveurs**. Sa tuile porte le statut de réconciliation (réconcilié / divergent /
|
||
non instancié) ; son détail réunit l'**identité éditable** (plan), les **dérivés**
|
||
(VMID/IP/VLAN), les **groupes**, les **applications et bases hébergées**, et
|
||
**Vérifier/Déployer**. La navigation clavier (`1-3`, `j/k`, `v/d`, `/`) et le
|
||
filtre opèrent désormais sur les serveurs. Un bandeau « Comment lire ce parc »
|
||
explicite le modèle serveur·hôte·groupe·application·base. Code mort retiré
|
||
(`carte`, `renduChaine`, `ONGLETS`, `champLecture`, sélection d'hôte, etc.).
|
||
|
||
### Ajouté
|
||
- **Fluidité d'exploitation du GUI** (sans dépendance, stdlib pure) :
|
||
- **Notifications empilées (toasts)** auto-effaçables au lieu d'une bannière unique
|
||
écrasée ; les erreurs restent plus longtemps, les opérations longues mettent à
|
||
jour leur propre toast.
|
||
- **Durée d'exécution** affichée en direct dans la console (Vérifier / Déployer) et
|
||
suivi vivant de « Appliquer le plan ».
|
||
- **Navigation clavier** : `1-5` changent de vue, `j/k` parcourent les hôtes,
|
||
`v`/`d` vérifient/déploient l'hôte sélectionné, `/` cible le filtre, `Ctrl+S`
|
||
sauvegarde la vue éditable courante (ignorés pendant la saisie).
|
||
- **Validation inline** des champs du plan (vue Serveurs) : nom `fonction-NN`,
|
||
mémoire, cœurs, disque — liseré rouge et blocage avant l'envoi.
|
||
- **Garde-fou anti-perte** : confirmation `beforeunload` si des éditions de plan ne
|
||
sont pas sauvegardées.
|
||
- Le bouton **Sauvegarder** de l'en-tête devient contextuel (sauve la vue éditable,
|
||
désactivé en lecture seule) — fin de l'impasse 409 ; suppression du code mort.
|
||
|
||
### Ajouté
|
||
- **Vue Serveurs : champs alimentés par les paramètres globaux.** À `+ Serveur`,
|
||
**Nœud** et **Stockage** deviennent des listes déroulantes (catalogues
|
||
`proxmox_noeuds` / `proxmox_stockages`, vide = défaut global) et **Intégrations**
|
||
une rangée de cases à cocher des rôles `client_*` disponibles (au lieu d'une saisie
|
||
texte). Les catalogues Nœuds / Stockages / Ponts sont de nouveaux intrants
|
||
(classe « Catalogue ») éditables dans le panneau « Intrants de base » et stockés
|
||
dans `group_vars/proxmox.yml` ; l'API expose `integrations_disponibles` (scan de
|
||
`roles/client_*`). Le type `liste` (chaîne virgulée → liste YAML dédoublonnée) est
|
||
ajouté au schéma des intrants.
|
||
|
||
### Supprimé
|
||
- **Vue « Chaîne » du bandeau (redondante).** Son contenu (`renduChaine`) était déjà
|
||
rendu, par hôte, dans l'onglet « Chaîne » du détail de la vue Inventaire ; la vue ne
|
||
faisait que le répéter pour tous les hôtes à la fois, sans réseau/proxmox ni boutons
|
||
d'opération. Retirée (bouton, rendu `dessinerArbre`, CSS `.arbre-*`) ; la chaîne
|
||
reste consultable hôte par hôte. Raccourcis clavier ramenés à 1-4.
|
||
|
||
### Modifié
|
||
- **Vue Inventaire (cartes) : détail converti en inspection lecture seule.** La vue
|
||
affichait « généré depuis le plan (lecture seule) » tout en exposant une surface
|
||
d'édition (+ Hôte, ✕, bascule actif/planifié, ✨ Proposer, champs et cases de
|
||
groupes éditables) qui ne pouvait pas être persistée (l'inventaire est généré ;
|
||
`/api/inventaire` renvoie 409). Ces contrôles orphelins sont retirés : nom, champs
|
||
Réseau/Proxmox et groupes en lecture seule, état affiché en badge. **Vérifier /
|
||
Déployer** et les onglets d'inspection sont conservés. L'édition reste dans les vues
|
||
**Serveurs / Applications / Bases**. Code mort supprimé (`ajouterHote`,
|
||
`supprimerHote`, `definir`, `definirNom`, `definirEtat`, `basculerGroupe`,
|
||
`proposer`, `autoProposer`, `prochainSeqLibre`, `marquerModifie`, état `modifie`).
|
||
- **Référence des paramètres de `make config`** (`docs/config-proxmox.md`).
|
||
Explique un par un les 16 paramètres Proxmox non sensibles + les secrets API
|
||
demandés par l'assistant : sens, valeur par défaut, quoi saisir, et quels champs
|
||
sont des constantes vs des défauts surchargeables par hôte. Renvois ajoutés depuis
|
||
`make help` et `QUICKSTART.md`. Comble un trou : ces invites n'étaient expliquées
|
||
nulle part de façon pérenne (impératif « exploitable sans IA »).
|
||
- **Panneau « Intrants de base » dans le GUI.** Un bouton ⚙ Intrants ouvre une fenêtre
|
||
unique pour saisir les valeurs communes à tout l'écosystème, avec une distinction
|
||
visible entre **constantes** (valeur unique, non surchargeable) et **défauts**
|
||
(valeurs proposées, surchargeables dans les instances). Les secrets ne sont **jamais**
|
||
saisis ni affichés ici (garde-fou `INTRANTS_CLES_INTERDITES` + filtrage par schéma) :
|
||
ils restent dans le Vault Ansible, édités en CLI ; le panneau les liste seulement à
|
||
titre informatif. Un changement de `domaine_interne` (clé de voûte) demande une
|
||
confirmation explicite ; un `domaine_interne` vide est refusé côté serveur. La
|
||
nomenclature reste en lecture seule (modifiable dans le plan). Voir
|
||
`docs/intrants-communs.md` et `docs/intrants-base-gui-conception.md`.
|
||
- **Source unique d'identité partagée.** `domaine_interne` et `chezlepro_timezone`
|
||
vivent désormais dans `inventories/partage/intrants-identite.yml`, référencé par
|
||
symlink depuis chaque environnement (`group_vars/all/10-intrants.yml`) — fin de la
|
||
duplication lab/production.
|
||
- **Info-bulles d'aide sur les champs du GUI.** Survoler la description d'un champ à
|
||
saisir affiche une bulle avec des instructions sommaires.
|
||
|
||
## 2026-06-28
|
||
|
||
### Ajouté
|
||
- **Document de présentation de l'écosystème Chezlepro** (`docs/ecosysteme-chezlepro.md`).
|
||
Description vulgarisée à destination client/partenaire : les piliers de l'écosystème
|
||
souverain, le modèle reproductible (plan → génération → clonage → conformité), et un
|
||
inventaire des **mesures de renforcement** réellement en place (SSH durci, fail2ban,
|
||
sysctl, nftables, AppArmor, auditd, mises à jour automatiques, gestion des secrets,
|
||
garde-fous destructifs, sécurité par l'architecture). Honnête sur le statut
|
||
« défini/validé vs déployé ».
|
||
|
||
### Ajouté
|
||
- **Dimensionnement dérivé des ressources VM.** Les cœurs/RAM/disque d'une VM sont
|
||
désormais **estimés depuis les logiciels hébergés + le socle SE**, au lieu d'hériter
|
||
des specs du golden template (CPU/RAM identiques pour toutes les VM auparavant).
|
||
Chaque rôle déclare son empreinte (`roles/<rôle>/meta/empreinte.yml`) ; le
|
||
générateur somme par hôte (marge + arrondis) et écrit `proxmox_coeurs`/
|
||
`proxmox_memoire`/`proxmox_disque_taille` ; le clonage passe `cores`/`memory` à
|
||
Proxmox (`omit` si absent → aucune régression). Override par hôte possible dans le
|
||
plan (`serveurs.yml`). Voir `docs/dimensionnement-ressources.md`.
|
||
|
||
### Corrigé
|
||
- **`make instancier-appliquer FORCE=1` n'honorait pas `--force`.** La recette Makefile lançait `instancier.py appliquer` sans relayer `FORCE` ; le message « Utilise FORCE=1 » était donc trompeur (un changement de plan intentionnel restait bloqué). La recette passe désormais `$(if $(FORCE),--force)`. Trouvé en dogfooding.
|
||
|
||
## 2026-06-24 — Première publication publique
|
||
|
||
Première mise à disposition publique de **Set-OPS**, moteur Ansible d'écosystèmes
|
||
numériques souverains sur Proxmox — offert à la communauté québécoise par
|
||
l'**Alliance Boréale**, à la Saint-Jean-Baptiste 2026.
|
||
|
||
- **Moteur générique, piloté par un plan déclaratif** : on édite le plan
|
||
(`instance/plan/*.yml`), l'inventaire Ansible se génère, les VM se clonent depuis
|
||
un golden template Debian 13, les rôles s'appliquent par groupes. Tout passe par
|
||
`make`, le GUI local et la documentation.
|
||
- **Piliers d'un écosystème souverain** : socle Debian durci, AC/PKI interne, DNS
|
||
interne, identité (LDAP + SSO), relais courriel, bases de données, observabilité,
|
||
forge.
|
||
- **Catalogue de modèles prêts à déployer** (`exemples/modeles/`) : un hébergeur
|
||
copie un modèle, le renseigne à ses couleurs, et instancie.
|
||
- **Souveraineté jusqu'au bout** : Set-OPS s'exploite entièrement à la main, sans
|
||
aucune IA.
|
||
|
||
Pour démarrer : **`QUICKSTART.md`**.
|