Commit graph

109 commits

Author SHA1 Message Date
44ee6d4d36 keycloak : URI de retour apres deconnexion, derivees de web_origins
Nextcloud se connectait et echouait a la deconnexion — « invalid redirect
uri ». Keycloak valide ces URI SEPAREMENT des URI de rappel, et aucun des
quatre clients ne declarait l'attribut ; Nextcloud etait seulement le seul a
en envoyer une.

post.logout.redirect.uris derivee de web_origins, qui porte deja l'URL de base
du service. Posee par l'API : attributes est une map, et kcadm -s sur une map
accepte sans ecrire (meme leçon que smtpServer ce matin). Relu apres ecriture.

Le second passage a revele un defaut de l'heure precedente : la tache de
journalisation se declarait changed a chaque deploiement — Jinja rendait True
et 1209600 en CHAINES, la comparaison au reel ne pouvait aboutir. Compose en
une seule expression, types natifs. Deux passages a changed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:24:02 -04:00
eef44c2dce nginx : dimensionner les tampons d'en-tetes pour les sessions OIDC
oauth2-proxy journalisait AuthSuccess pendant que le navigateur recevait une
erreur de passerelle : le cookie de session porte le jeton d'identite, decoupe
en plusieurs Set-Cookie, et le tampon par defaut de nginx (4 Ko) ne peut pas
les contenir.

  upstream sent too big header while reading response header from upstream
  server: icinga.chezlepro.internal, request: GET /oauth2/callback

Pose sur TOUTES les expositions : c'est une propriete du proxy, pas de ce
service-la, et le prochain service derriere un IdP rencontrerait le meme mur.

Trouve grace au journal d'evenements active juste avant : LOGIN puis
CODE_TO_TOKEN reussis pour icingaweb2 ont ecarte l'identite d'un coup.

Laisse ouvert : trois upstream timed out vers Keycloak en 14 h, alors que
Keycloak repond en millisecondes depuis l'edge. Cause non etablie — et ma
sonde MTU ne valait rien, l'ICMP etant bloque par construction entre hotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:06:05 -04:00
dd84ed011c keycloak : journaliser les evenements du realm
Deux services n'aboutissaient pas et tout etait correct cote serveur — clients
OIDC, URI de rappel, secret identique (meme empreinte), CA de confiance,
aucune restriction de domaine. Le premier saut rejoue au curl montrait des
parcours sains.

Et la, plus rien a examiner : eventsEnabled = False. Keycloak ne gardait
aucune trace, ni des connexions ni des echecs. Manque de diagnostic, mais
surtout d'exploitation : « un sysadmin l'exploite sans IA » suppose qu'il
puisse lire lui-meme ce qui s'est passe.

Journal (connexions + actions d'administration, retention 14 jours)
reconcilie par le role, pas active a la main dans une console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 10:57:42 -04:00
a90dff7ba7 courriel : routage local par identifiant, et la livraison interne remise en marche
Arbitrage rendu : le courrier local est route par uid, plus par l'attribut
mail. Dovecot le faisait DEJA (mail_home = /var/vmail/%{user | username}) ;
les deux moities ne s'accordaient que par coincidence, tant que mail valait
uid@<domaine_interne>. Cout assume : l'adresse interne est derivee et ne se
choisit plus ; en echange mail redevient libre de porter la vraie adresse de
la personne.

Puis la preuve de bout en bout a revele bien pire : toute livraison interne
etait DIFFEREE.

  SSL_connect error to infra-mail-01:24: Connection timed out
  status=deferred (Cannot start TLS: handshake failure)

Postfix etait durci (lmtp_tls_security_level = verify), le port LMTP de
Dovecot ecoutait en clair — son ssl = required global ne concerne que les
services de connexion. Les deux cotes d'un meme flux avaient ete traites
separement. Corrige et accorde : ssl = yes sur l'inet_listener (TLS implicite)
et lmtp_tls_wrappermode = yes cote client ; l'un sans l'autre ne marche pas.

Livraison prouvee : status=sent (250 ... Saved), message dans
/var/vmail/sysadmin/Maildir/.INBOX/new/.

Le devis disait CONFORME pendant ce temps : il verifiait la resolution et les
dialectes, jamais si le courrier BOUGE. Un devis qui ne regarde que les
reglages ne dit pas si le service rend son service. Il releve desormais la
file d'attente et ses raisons ; test negatif : la panne est nommee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 09:42:45 -04:00
5fde136e9f devis des certificats : disque contre memoire, et l'AC etait expiree
Deuxieme application du patron devis/applicateur aux services. Trouve a la
premiere execution : sur infra-pki-01 — l'autorite elle-meme — le certificat
etait expire depuis plus de 8 h et le renouvellement echouait toutes les 14
minutes sur « 'step ca renew' requires the '--ca-url' flag ». Rien ne le
signalait.

Cause : sur l'hote de l'AC, /etc/step est le STEPPATH du SERVEUR, pas un
amorcage client — pas de defaults.json, et l'unite de renouvellement en
dependait. La lecon etait deja ecrite dans le commentaire de la tache
d'emission (« l'autorite ne bootstrape pas »), jamais reportee sur l'unite.

Le role ne pouvait pas non plus se soigner : la re-emission ne regardait que
la FORME (cert absent ou SAN manquant), jamais la validite. client_pki
verifie desormais l'echeance (client_pki_marge_renouvellement).

Ce qu'il a fallu desapprendre : les certificats vivent 24 h et se renouvellent
toutes les ~14 min ; « empreinte servie != empreinte disque » est l'etat
NORMAL. Comparer les empreintes aurait donne un verificateur qui crie en
permanence. Le signal est l'echeance de ce qui est SERVI, plus l'absence de
client_pki_reload_services.

Le devis a d'abord menti, du defaut meme qu'il traque : include_vars au niveau
du play prime sur les group_vars. Et le premier correctif a PARU marcher —
set_fact accepte un dictionnaire entier en argument libre sans erreur et n'en
fait rien. Il faut reimposer cle par cle. Les deux devis sont corriges et le
piege est consigne dans docs/devis-services.md avant d'ecrire le prochain.

Verifie dans les deux sens : CONFORME sur 14 hotes ; sur un releve ou l'on
rejoue une copie perimee en memoire, 2 ecarts et code de sortie 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 07:20:56 -04:00
419ecaba63 identite : une declaration de politique de mot de passe, deux executants
Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une
valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction.

1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde :
   « abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204),
   puis actif pour l'authentification. Keycloak ecrivait userPassword en
   direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy.
2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le
   depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction
   from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact
   reconcilie la politique et l'overlay (DN lu, pas devine).
3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais
   ou=people — acces web, aucune boite, invisible du modele de groupes.
4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ».

Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy,
passwordPolicy et anti-force-brute. Les deux roles la consomment sans la
redeclarer.

usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est
porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles
de qualite au rootDN. La premiere seule aurait paru juste sans tenir.

Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de
passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present
dans ou=people. Second passage des deux playbooks : changed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:28:10 -04:00
775df924cb identite : « mot de passe oublie » — raccord SMTP derive du plan
Le realm portait une politique d'acces complete et aucun moyen d'ecrire a qui
que ce soit. Tout oubli remontait donc a l'exploitant, qui n'avait d'autre
choix que de manipuler le mot de passe d'autrui.

- serveur_keycloak/tasks/courriel-realm.yml : reconcilie smtpServer et
  resetPasswordAllowed ; hote du relais DERIVE de applications.postfix.hote,
  et refus explicite si le plan ne declare pas de MTA.
- passe par l'API d'administration : kcadm.sh accepte les deux formes -s sur
  une map, sort en succes et n'ecrit rien (smtpServer reste vide).
- amorcage_acces_courriel redevient a declarer : cette adresse designe une
  personne, hors du systeme qu'on amorce ; une boite interne serait illisible
  tant qu'on n'a pas l'acces qu'on cherche justement a recuperer.
- autorisation.md §6.6 : le mecanisme, ses deux conditions, et l'ecart
  d'adresse laisse par l'ancien mode READ_ONLY de la federation.

Preuve : banniere SMTP lue depuis idm-01, RCPT TO accepte, execute-actions-email
declenche, MTA en starttls -> relay=mx.chezlepro.ca status=sent (250). Second
deploiement changed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:08:06 -04:00
4642ffec0a rotation : vault_openldap_admin, quatre consommateurs et deux pieges
`ldappasswd` ne peut pas le changer : `cn=admin` n'est pas une entree de la base
mais le rootDN declare dans cn=config. Le mot de passe vit dans `olcRootPW` et se
modifie par un bind EXTERNAL. L'echec etait sans degat — l'ancien fonctionnait
toujours, verifie avant de continuer.

Keycloak stocke le mot de passe de LIAISON dans sa base et le masque : la
reconciliation d'hier couvrait l'URL, les DN et le mode, pas `bindCredential`.
Tourner le secret aurait coupe Keycloak de l'annuaire. Comme la valeur est
masquee, la reconciliation passe par une empreinte.

Verifie consommateur par consommateur : synchro LDAP de Keycloak (qui prouve la
liaison), carte LDAP de Postfix, Dovecot actif. Les trois rejouent a changed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 05:46:38 -04:00
35602546eb rotation : vault_forgejo_oidc, et la reconciliation des secrets OIDC
Le secret avait fui dans une sortie de diagnostic. Le faire tourner a d'abord
demande de rendre la rotation possible : ni Keycloak ni Forgejo ne reconciliaient
un secret OIDC existant. Le commentaire de clients-oidc.yml l'avouait
(« create-si-absent »), et `update-oauth` ne passait pas --secret.

Regenerer la voute aurait laisse les deux cotes sur l'ancienne valeur — ou un
seul des deux, et le SSO aurait casse sans que rien ne l'annonce.

Keycloak compare desormais le secret EN PLACE a celui voulu avant d'ecrire.
Verifie par empreinte aux trois endroits : voute, Keycloak, Forgejo — identiques.
Keycloak rejoue a changed=0.

Signale sans etre corrige : `Deployer app.ini` change a chaque passage. Forgejo
reecrit lui-meme ce fichier (il y persiste ses secrets generes) et le gabarit
l'ecrase. Prealable au correctif : decider quelles cles appartiennent au gabarit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:20:11 -04:00
91d3a736e7 rotation : les comptes de secours peuvent enfin changer de mot de passe
Le runbook promettait cette rotation ; le code ne savait pas la faire. Les trois
roles ne posaient le mot de passe qu'a la CREATION — regenerer la voute aurait
produit le mensonge silencieux corrige toute la journee.

Chaque role sait desormais CHANGER un mot de passe existant, idempotent par
empreinte du secret applique. Pour Nextcloud le secret passe par l'environnement,
pas par la ligne de commande ou il serait visible dans la table des processus.

`grafana-cli` ecrivait dans une base FANTOME en annoncant « changed successfully »
a chaque fois : il prend `paths.data` a `<homepath>/data`, le paquet Debian range
la base dans /var/lib/grafana. Demasque par le champ `updated` du compte, reste a
l'heure du deploiement initial malgre quatre reinitialisations « reussies ». Un
message de succes n'est pas une preuve ; l'etat l'est.

Verifie par authentification reelle : Grafana 200, Nextcloud 200. Forgejo ferme
l'API par doctrine (D-41) — seule preuve disponible : le retour de la commande.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 21:10:47 -04:00
df8ffb68d6 icingaweb2 : habilitation par groupe d'annuaire, plus par liste d'uid
Le dernier `porte_par: liste-uid` du catalogue. `roles.ini` porte desormais
`groups = "sysadmin"` ; `serveur_icingaweb2_admins` devient un repli de
depannage, VIDE par defaut.

Le mode SSO complique le montage : les membres d'un `groupOfNames` sont des DN,
alors que `REMOTE_USER` est une chaine. Un backend LDAP supplementaire est
declare — jamais utilise pour authentifier — uniquement pour que `groups.ini`
resolve le nom vers son DN. Sans ce pont, l'habilitation par groupe est
impossible en SSO.

NON PROUVE : la resolution REMOTE_USER -> DN -> appartenance est interne a
Icinga Web 2 ; seule une connexion reelle par le SSO la confirmera.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:54:41 -04:00
1b16e67cdc keycloak : realm-admin par appartenance, et la boucle de mot de passe
`realm-admin` (role du client `realm-management`) est attache au GROUPE sysadmin.
Administrer le realm ne passe plus par le compte local. Portee : CE realm, jamais
`master` — le compte de secours reste hors d'atteinte du groupe, et c'est le sens
meme d'un acces de secours (D-40).

Les roles de CLIENT sont un espace de noms distinct : la declaration gagne
`roles_client`. L'API attend l'UUID du client, pas son clientId — interroger par
le nom rendait une erreur, la verification echouait toujours et la tache se
declarait `changed` a chaque passage. Deux passages consecutifs a changed=0.

La boucle de changement de mot de passe : `pwdMustChange: TRUE` signifie « quand
un ADMINISTRATEUR pose un mot de passe, l'utilisateur doit le changer ». Keycloak
ecrit en tant qu'administrateur — chaque changement relaye etait vu comme une
reinitialisation. Incompatible par construction avec un IdP qui relaie.

La contrainte est deplacee la ou l'utilisateur la voit : pwdMustChange FALSE cote
annuaire, action `UPDATE_PASSWORD` posee par Keycloak. J'avais eprouve pwdReset
au niveau LDAP, ou il marche, sans parcourir le chemin complet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:47:10 -04:00
71ed179b0c keycloak : federation LDAP en WRITABLE, et reconciliee
Le changement de mot de passe imposé echouait sur « Federated storage is not
writable » : `editMode=READ_ONLY` etait code en dur. Keycloak lisait l'annuaire
sans pouvoir y ecrire — `pwdReset` devenait un cul-de-sac, LDAP exigeant un
changement que Keycloak ne pouvait pas faire.

Trois modes, un seul tient avec la doctrine. `UNSYNCED` ferait ecrire Keycloak
dans SA base : Dovecot et Postfix, qui se lient directement a LDAP (D-39),
valideraient encore l'ancien mot de passe. Ca aurait « marche » a l'ecran en
cassant le courriel en silence. `WRITABLE` ecrit A TRAVERS : l'annuaire reste la
source unique, Keycloak n'en est qu'un client.

Le mode devient une variable et il est RECONCILIE : un provider cree en
READ_ONLY le serait reste a vie.

Deux fautes de ma part au passage : extraction ancree sur `$` alors que la ligne
finit par un guillemet, et pas de `|| true` — sous `pipefail`, un grep vide tue
le script. Masque par `no_log`, pour la troisieme fois aujourd'hui.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 18:16:34 -04:00
c79aae200d flux : admin devient un pair, et make ca-racine livre la racine
Le sysadmin ne pouvait atteindre AUCUNE interface web de la flotte qu'il
administre : le pare-feu de l'hyperviseur n'autorisait le 443 de l'edge que
depuis `+t17-flotte`. Le reseau d'administration est dans `t17-admin`.

L'exploitant arrive par un TROISIEME chemin que rien ne declarait : ni `externe`
(Internet, affaire de la frontiere), ni `flotte` (le tenant). Le runbook de
reprise supposait pourtant qu'on ouvre Keycloak dans un navigateur.

`admin` devient un pair declarable — les reseaux de `nftables_admin_ssh`, deja
source unique de la garde anti-lockout. `serveur_nginx` le declare pour son 443.
L'edge SEUL : ouvrir les services en direct elargirait la surface pour rien.

Erreur de methode de ma part : mon premier test utilisait `/dev/tcp` et concluait
« atteignable ». Faux — la frontiere repond au SYN a la place de la cible. Je
l'avais consigne le matin meme.

`make ca-racine` / `make ca-empreinte` : l'hote de l'AC est derive du groupe
`serveur_step_ca`, et la sortie insiste sur la comparaison d'empreinte. La racine
est un certificat PUBLIC — hors voute, dans le magasin de confiance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:26:35 -04:00
cc6026641f empreintes muettes, courses de premier demarrage, collision de noms
Quatre meta/empreinte.yml declaraient leurs valeurs A LA RACINE, sans la cle
`setops_empreinte:` : collabora, nextcloud, web_dorsal, web_frontal. Le lecteur
les voyait vides et rendait {0,0,0} — en silence. collab-01 s'est retrouvee avec
1 coeur / 1 Go pour porter Nextcloud ET Collabora, et a cesse de repondre en SSH
faute de memoire. Corrigee : 4c/5632Mo. Une garde refuse desormais cette forme.

Un fichier qui existe mais ne dit rien est pire qu'un fichier absent : le repli
aurait donne des valeurs sensees.

Deux courses de premier demarrage :
- le clone rend la main avant que son .conf existe -> attente active sur l'API ;
- le verrou dpkg frappait hors de `common_packages` -> `lock_timeout` pose en
  module_defaults sur les 30 playbooks de groupe, une declaration au lieu de 30.

Au passage, j'ai failli livrer pire que le defaut : une URL coupee avec `>-`
inserait une ESPACE en son milieu. Le lint passait, la requete non.

Enfin : `proxmox_kvm` identifie une VM par son NOM. Une VM heritee homonyme lui
a fait rapporter `ok` sans rien cloner — un deploiement peut donc PARAITRE
reussi alors qu'aucune VM n'existe. Touche D-37 directement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:22:38 -04:00
6699935dc7 resoudre_idp : le nom invente etait dans QUATRE roles
`forge-01` a echoue sur une URL de decouverte pointant
`https://keycloak.<domaine>` — le nom que rien ne publie. J'avais corrige
exactement ca dans `serveur_oauth2_proxy` quelques heures plus tot, en croyant
regler un cas isole.

Il etait dans quatre roles : forgejo, grafana, nextcloud, oauth2-proxy. Chacun
fabriquait le meme nom par la meme convention. Une cinquieme correction a la main
aurait diverge comme les quatre autres.

`roles/resoudre_idp` lit l'exposition declaree au plan et rend hote, base et
discovery. Les roles n'en gardent qu'un REPLI nomme, jamais la valeur de travail.

Verifie en base sur forge-01 : discovery = auth.chezlepro.internal,
GroupClaimName = groups, AdminGroup = sysadmin.

Meme lecon que `resoudre_annuaire` hier : corriger la valeur la ou elle echoue ne
corrige que la. Ce sont les copies silencieuses qui coutent la journee suivante.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:33:32 -04:00
a08c61bb4e acces : Forgejo et Nextcloud cables, et le claim groups emis
Les deux services n'etaient pas encore deployes : les cabler maintenant vaut
mieux que les corriger apres. `porte_par` passe de `aucun` a `claim-groupe`.

Forgejo recoit --group-claim-name + --admin-group, et sa tache passe de « creer
si absent » a add-oauth OU update-oauth : le meme defaut que la federation
Keycloak — cree une fois, jamais corrige — l'attendait sinon.

Nextcloud recoit --mapping-groups + --group-provisioning, plus une tache qui
verse les membres du groupe d'habilitation dans le groupe interne `admin` : etre
dans un groupe projete ne donne aucun pouvoir en soi.

Le maillon qui manquait aux deux : AUCUN mapper de protocole n'emettait le claim.
Les groupes existaient dans le realm et n'apparaissaient dans aucun jeton — un
cablage correct des deux cotes et rien au milieu. `oidc-group-membership-mapper`
pose sur les trois clients, `full.path=false` pour que le claim porte `sysadmin`
et non `/sysadmin`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:49:28 -04:00
74a2456f09 keycloak : group-ldap-mapper — le role s'attache au GROUPE
La chaine est complete : LDAP cn=sysadmin -> groupe de realm -> role
grafana-admin -> Admin dans Grafana. Ajouter quelqu'un au groupe dans l'annuaire
lui ouvre Grafana sans deploiement et sans que personne ne soit nomme (D-66).
Rejoue : changed=0.

Defaut de fond trouve en chemin : la federation LDAP n'etait JAMAIS reconciliee.
`federation-ldap.yml` creait le provider s'il manquait puis ne le corrigeait
plus — il pointait encore `ldaps://id-ldap-01...`, le nom errone corrige le matin
meme dans `resoudre_annuaire`. La synchronisation echouait sur `UnknownHost`.

C'est le revers de D-67 au mauvais endroit : l'INFRASTRUCTURE se reconcilie,
seules les appartenances ne le sont pas. URL, usersDn et bindDn sont desormais
corriges a chaque passage.

Et j'avais masque l'echec avec `|| true` : le mapper existait, le realm restait
vide, rien ne disait pourquoi. Retire — l'erreur remonte avec son message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:32:47 -04:00
6f2acafdb6 acces : les cinq meta/acces.yml, et le mecanisme qui manque a chacun
Chaque role web declare le GROUPE qu'il reconnait et ce qu'il lui accorde. Un
troisieme champ s'est impose en ecrivant : `porte_par` — le mecanisme qui
transporte reellement l'habilitation. Sans lui, les declarations auraient decrit
une chaine inexistante.

Etat mesure : grafana `role-realm` (reel) ; forgejo, nextcloud et keycloak
`aucun` ; icingaweb2 `liste-uid` — il NOMME DES PERSONNES, ce que D-66 interdit.

Le maillon manquant est chez Keycloak : `role_assignments` assigne un role a un
UTILISATEUR, et son propre commentaire l'admettait (« en prod, preferer
l'assignation via groupe d'annuaire »). Sans group-ldap-mapper, les groupes LDAP
n'atteignent jamais les services. Sa meta a donc une autre forme,
`acces_projection` : Keycloak projette au lieu de consommer (D-65).

Defaut corrige : `serveur_icingaweb2_admins` valait "testmail", un compte de
test code en dur dans le moteur qu'aucune instance ne surchargeait — le seul
administrateur declare de la supervision etait un utilisateur inexistant. Il
suit desormais l'uid d'amorcage. Verifie sur mon-01 : users = "sysadmin".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 13:59:33 -04:00
69536a2b1a openldap : charge ppolicy — le jeton d'amorcage devient a usage unique
Le module etait sur disque mais jamais charge (seul back_mdb l'etait). Depuis
OpenLDAP 2.5 son schema est INTEGRE au module : aucun .ldif a charger.

La contrainte mord, mesuree sur un compte fraichement amorce :

  Insufficient access (50)
  Operations are restricted to bind/unbind/abandon/StartTLS/modify password

Le sysadmin peut se connecter et RIEN d'autre que changer son mot de passe. Ce
que la doctrine promettait est garanti techniquement, plus seulement demande.

`pwdMustChange` est ce qui donne son effet a `pwdReset` : sans lui, marquer une
entree n'oblige a rien. La politique apporte aussi longueur minimale 12,
verrouillage apres 5 echecs, historique. `olcPPolicyUseLockout` reste FALSE :
annoncer « compte verrouille » renseignerait un attaquant sur son existence.

Le DN de la base est LU, pas suppose : olcDatabase={1}mdb est l'usage mais
l'index n'est pas garanti.

La detection du role d'amorcage s'est verifiee d'elle-meme : rejoue apres le
chargement, il annonce « Changement FORCE » la ou il disait l'inverse une heure
plus tot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 13:52:00 -04:00
ba70b80197 amorcage_acces : le role qui cree UN acces puis se retire
Cree uid=sysadmin et cn=sysadmin dans LDAP. Prouve sur idm-01 : le compte
s'authentifie, et le second passage ne touche a rien (changed=0, 7 taches
sautees) — idempotence par EXISTENCE, pas par conformite (D-67).

Il suit l'annuaire au lieu de se declarer au plan : il ecrit par `ldapi:///` et
doit tourner sur cet hote. Le declarer comme groupe obligerait chaque instance a
le poser sur le bon hote, et elles ne le nomment pas pareil (idm-01 ici,
id-ldap-01 chez Technolibre) — je l'ai d'abord pose sur infra-pki-01 par erreur.

Trois defauts trouves en le construisant :

1. `pwdReset` n'existe pas dans ce schema (overlay ppolicy non charge) :
   l'entree entiere etait rejetee et `no_log` masquait la cause. J'avais suppose
   un mecanisme sans verifier. Le role le DETECTE maintenant, et la doctrine ne
   promet plus un changement force qui n'a pas lieu.

2. Le mot de passe aurait ete stocke EN CLAIR : `ldap_entry` ecrit userPassword
   litteralement. Hache par `slappasswd -h {SSHA}` desormais.

3. `voute.py` ne scannait que roles/<groupe> : un role applique par un playbook
   sans etre un groupe echappait au recensement, ce que D-20 interdit. Il suit
   maintenant les listes `roles:` des playbooks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 13:42:27 -04:00
8260f2f914 annuaire : l'hote se derive du plan, il ne s'ecrit plus
`resoudre_annuaire` fixait `id-ldap-01` en dur — une machine qui n'existe dans
aucun plan. Postfix ne pouvait pas se lier : « Can't contact LDAP server ».

L'intention etait juste et son commentaire le disait : un seul point ou le nom
est fixe, au lieu d'etre repete dans chaque role. Mais ECRIT au lieu d'etre
derive. Une valeur unique et fausse vaut mieux qu'une valeur repetee et fausse ;
elle reste fausse.

Le plan le declare (`applications.openldap.hote`) : c'est de la qu'elle vient.

infra-mail-01 et edge-mta-01 deployees, 19 playbooks sans un echec. LMTP en TLS
verifie, mynetworks sur le supernet derive, liaison LDAP etablie vers idm-01.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:46:48 -04:00
249fba702a sso : l'issuer et le nom d'hote se derivent de l'exposition declaree
oauth2-proxy fonctionne devant Icinga Web 2 : 302 vers Keycloak, qui federe
LDAP. Quatre defauts leves, dont trois du meme motif — un nom construit par
convention d'un cote, declare de l'autre.

1. Le secret de cookie etait en base64 STANDARD. oauth2-proxy decode en
   url-safe : le decodage echoue, il retombe sur la chaine brute et se plaint de
   sa LONGUEUR, jamais de l'encodage. Garde ajoutee qui refuse + et /.

2. L'issuer etait fabrique (`keycloak.<domaine>`), un nom que rien ne publie.
   Le plan expose `auth.<domaine>` — c'est ce nom que le DNS resout et que nginx
   sert. Derive desormais du champ `expose`.

3. Keycloak s'annoncait sous ce meme nom invente : « issuer did not match ».
   Meme correctif a la source.

4. Le pare-feu est-ouest bloquait l'edge : nginx ne declarait son 443 qu'avec
   `pair: externe`, que le devis est-ouest saute (il releve de la frontiere).
   Le flux existait d'un seul cote et la matrice etait satisfaite. nginx declare
   maintenant aussi son 443 depuis la flotte.

obs-01 et mon-01 deployees. 7 cibles Prometheus up, scrutees en TLS a travers
cinq zones du VRF.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 11:47:34 -04:00
41413c7f04 deploiement : deux attentes de premier demarrage, et trois defauts leves
`idm-01` a prouve le routage inter-zone dans le VRF (10.27.19.21 -> 10.27.17.11,
deux passerelles anycast) et leve trois defauts qu'aucun devis ne pouvait
montrer :

1. Le handler de `client_pki` rechargeait `slapd` avant son installation —
   consequence directe de l'ordre retabli. Un consommateur absent n'est pas une
   erreur ; un vrai echec de rechargement reste fatal.

2. `resoudre_base` ne trouvait aucune base de portee `application` : Keycloak
   declare `consommateur: keycloak`, le role cherchait `serveur_keycloak`. Le
   lien est declare dans applications.yml — on le suit au lieu de retirer un
   prefixe a la main.

3. Keycloak attend PostgreSQL, pas encore deploye. Pas un defaut : `deployer`
   ne connait que l'ordre intra-hote, l'ordre inter-hotes est celui de `site`.

Et deux attentes, sans lesquelles on ne peut pas enchainer creation et
deploiement — donc sans lesquelles `myDay` ne reconstruit pas seul : SSH, puis
cloud-init et les maj automatiques. `creer-vm` rend desormais une VM PRETE.
`lock_timeout: 300` sur chaque tache apt complete l'attente : le verrou peut
etre repris ENTRE deux taches, ce qu'une verification en amont ne previent pas.

Defaut dans l'attente elle-meme : `unattended-upgrades` est un demon, toujours
actif — l'inclure rendait la condition impossible a satisfaire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 09:30:48 -04:00
f18715bc78 deployer : l'ordre des couches, et l'autorite qui se signe elle-meme
`make deployer` triait les groupes alphabetiquement apres le socle :
`client_metrique` passait avant `serveur_step_ca`, donc exigeait un certificat
que l'autorite, pas encore deployee, ne pouvait pas avoir emis — sur l'hote de
l'AC lui-meme. `docs/couches-deploiement.yml` existe pour definir cet ordre et
dit « integrations deployees en dernier » ; `make site` le lit, `deployer` ne
l'avait jamais lu. Meme registre desormais.

Deux politiques universelles se contredisaient : `client_pki` exemptait l'AC,
`client_metrique` refuse toute exemption. L'exemption confondait « ne pas
s'enroler » et « ne pas avoir de certificat ». `client_pki` distingue les deux
chemins : bootstrap pour les autres, EMISSION LOCALE sur l'AC. L'exemption
disparait, la doctrine reste.

Effet de bord instructif : `step ca bootstrap` ecrit aussi le defaults.json qui
porte l'URL de l'AC. En sautant le bootstrap on perdait l'information sans le
voir. `--ca-url` et `--root` sont explicites pour tous les hotes.

Mesure : infra-pki-01 et infra-dns-01 entierement deployees, aucun echec,
step-ca health=ok, powerdns resout la zone souveraine, et les deux hotes portent
un certificat de l'AC interne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:57:01 -04:00
e276e76f32 dns : client_unbound universel, et un resolveur d'amorcage
Une VM ne peut pas s'installer sans resoudre des noms. PowerDNS repond
UNIQUEMENT pour la zone souveraine et ne recurse pour personne : il manquait un
resolveur recursif. `client_unbound` est l'outil ecrit pour ca — il rejoint
`client_journal` et `client_metrique` parmi les integrations universelles.

`infra-dns-01` est exempte (PowerDNS occupe son port 53), et
`serveur_powerdns_listen_addresses` passe de 0.0.0.0 a l'adresse de l'hote pour
laisser 127.0.0.1:53 libre. Mais l'appartenance au groupe est AUSSI ce qui ouvre
le port 53 a la frontiere : en exemptant la machine, je lui retirais le droit de
resoudre. `serveur_powerdns` declare donc son propre flux sortant — il ne
recurse pour personne, mais doit resoudre pour lui-meme.

Nouvel intrant `dns_amorcage`, derive jusqu'a `make creer-vm`. Cloud-init
l'ecrit bien mais sans effet : `dns-nameservers` exige `resolvconf`, absent du
gabarit, et installer resolvconf demande apt, qui demande la resolution.
`serveur_debian` pose donc le resolveur en pre_tasks, avant le premier apt, avec
une garde qui respecte la bascule ulterieure de client_unbound.

Defaut corrige en chemin : `_intrants_communs()` lisait `instance/` en dur ; le
chemin derive maintenant de l'inventaire recu, et un test le prouve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:41:58 -04:00
22ef279464 authentification : chaque rôle déclare sa position, gardé par P29
Une règle qu'aucune garde ne vérifie finit par ne plus être vraie — c'est ce qui
était arrivé aux 28 lignes d'intégration recopiées. Chaque rôle serveur_* porte
un meta/authentification.yml, confronté à son code par P29.

web-sso 5, socle-identite 2 (keycloak/openldap : ils SONT la chaîne d'identité),
ldap-direct 2, interne-sans-auth 2, sans-auth-humaine 12.

La preuve refuse l'oubli ET le mensonge. Éprouvée par sabotage sur sept cas :
déclaration supprimée, portée inventée, secours retiré, posture de formulaire
retirée, raison retirée, ldap-direct mensonger, réglage retiré des defaults.

Les deux derniers passaient dans la première version :

- le mensonge passait à cause d'un commentaire. Je cherchais le mot « ldap » dans
  le rôle, et serveur_grafana/defaults/main.yml contient « désactiver quelqu'un
  dans LDAP » : de la prose validait une déclaration fausse. La preuve exige
  maintenant un indice nommé — variable <rôle>_oidc / <rôle>_ldap, ou URI ldap://
- le réglage retiré passait parce que le gabarit citait encore la variable alors
  que plus rien ne lui donnait de valeur. La preuve lit defaults/main.yml en YAML
  et exige que la clé y soit définie, pas mentionnée.

Elle a aussi forcé une valeur : oauth2-proxy était déclaré « formulaire local
fermé » alors qu'il n'a aucun compte local. D'où formulaire_local: aucun, qui
distingue « il n'y en a jamais eu » de « il y en a un, il est fermé ».

Correction d'une note de la veille : Prometheus et Loki ne sont PAS exposés
publiquement (aucun expose au plan). Seuls six groupes le sont. Le risque est
intra-tenant, pas frontalier. Les deux lacunes sont comptées à chaque exécution,
pas masquées.

AFF-111, D-42. 29 preuves OK, ansible-lint (production) sur 375 fichiers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:05:43 -04:00
6a62f0a4c7 authentification : SSO Keycloak devant, secours par sudo, formulaire local fermé
Directive : toute authentification web passe par Keycloak, LDAP est la source
unique des comptes, chaque service garde un accès de secours par sudo sur
l'hôte. Les trois sont indissociables — la chaîne service → Keycloak → LDAP est
en série, donc sans secours une panne exclut tout le monde, y compris pour
réparer. Portée : le web seulement ; IMAP/SMTP se lient à LDAP directement et
SSH est en clé seule.

Posture <rôle>_connexion_locale, false par défaut. Le compte local existe — il
ne peut pas dépendre de Keycloak — mais son formulaire n'est plus proposé au
repos : ouvert en permanence, il contourne la politique de mot de passe, le MFA
et surtout la révocation centrale.

Vérifié auprès de l'amont, puis par rendu réel des gabarits dans les deux
postures :
- Grafana  GF_AUTH_DISABLE_LOGIN_FORM  → ferme ;
- Forgejo  ENABLE_INTERNAL_SIGNIN + ENABLE_BASIC_AUTHENTICATION → ferme, API
  Basic comprise. N'existe que depuis la v10 (ticket amont 7476) ; le rôle
  épingle 10.0.0 et un assert refuse la fermeture en deçà, car le réglage serait
  ignoré sans erreur ;
- Nextcloud hide_login_form → MASQUE seulement : ?direct=1 reste le chemin de
  secours documenté par l'amont. Écrit comme tel, sans prétendre à l'équivalence.

Défaut attrapé par le rendu : la condition Forgejo sans `| bool` n'émettait rien
dans aucune posture — une valeur en chaîne est vraie au sens Jinja, la connexion
locale serait restée ouverte en silence.

docs/authentification.md, décisions D-38 à D-41. ansible-lint (production) sans
échec, 28 preuves OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:50:36 -04:00
b0e56cbfc0 intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur
Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants.

1. Intégrations universelles (D-33/D-34, P26)

Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à
quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être
oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01
n'étaient ni supervisés, ni journalisés, ni certifiés.

Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le
plan ne garde que les vrais choix et refuse la recopie. Les exemptions se
dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle
pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace.

Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la
voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations
universelles auraient cessé d'être exigés et P18 serait passé au vert sur une
voûte incomplète.

Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41
lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas
client_pki sur infra-pki-01.

2. Vue Intégrations : la matrice

La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas
été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x
intégrations : colonnes de politique en lecture seule, facultatives cochables sur
place, ligne de couverture n/N qui rend le motif visible sans le juger.

3. Propriété des intrants (D-35/D-36, P27)

Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière.
Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de
stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont
dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive —
l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et
ses défauts de placement.

Le panneau nomme désormais le propriétaire de chaque section : éditer une section
« hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas.

26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
70fe6de557 flux : l'ICMP entre au registre — l'overlay à 1450 l'exige
Décision : l'overlay EVPN plafonne à 1450. Conséquence invisible : sous 1500,
tout ce qui traverse la frontière dépend de la découverte de MTU de chemin,
donc de l'ICMP « fragmentation nécessaire ».

Or le registre ne connaissait que TCP et UDP. Ce message ne pouvait pas être
déclaré et la bordure en `block in log all` l'aurait jeté : la connexion
s'établit, les petites requêtes passent, les grosses réponses restent
suspendues — la panne la plus coûteuse à diagnostiquer, et celle qu'on impute
d'abord à l'application.

`protocole: icmp` est admis ; le champ `port` y porte le type
(`frag-needed`). Le socle déclare les deux sens. Vérifié : nftables d'hôte
inchangés, le pair `externe` reste sauté.

Reconnaissance (lecture seule) : l'EVPN est à moitié construit — contrôleur
EVPN0017 (ASN 65000), zones VRF0011 et VRF0017, un VRF par tenant avec le VNI
égal à l'index. Aucun VNet, aucun nœud de sortie.

Signalé et non corrigé : les pairs BGP sont dans 10.27.19.0/24, le
sous-réseau Services-infra de Chezlepro. Le transport du cluster dérive de
l'index d'un tenant, et une VM de cette zone partage son sous-réseau avec les
VTEP — l'isolation est percée à l'endroit que l'EVPN devait fermer.

Preuves : 24 OK, 0 échec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:07:30 -04:00
4d37570c0c frontière : la sortie générale est déclarée, pas subie
Le devis se terminait par `block out log all` avec une seule règle sortante
(le relais SMTP). Appliqué tel quel, il coupait la flotte d'Internet : plus
d'apt, plus de NTP, plus de récursion DNS. Rien ne le signalait — la ligne
la plus lourde de conséquences du devis, posée à la suite des autres.

La sortie est déclarée dans le registre, donc dérivée :
- `serveur_debian` (socle, 14 hôtes) : 443 et 80 pour les dépôts apt, 123/udp
  pour l'horloge — une dérive fait échouer step-ca et le SSO des semaines
  après la cause ;
- `client_unbound` : 53 udp et tcp, la récursion depuis la racine qu'implique
  `client_unbound_transitaires: []`. Le TCP est le repli obligatoire dès
  qu'une réponse DNSSEC dépasse la taille UDP.

Le devis passe de 6 à 11 règles, et sa section 5 énonce le default-deny
sortant, le nombre de règles qui l'accompagnent, et où déclarer un besoin
oublié — jamais à la main dans le pare-feu.

Vérifié : les nftables d'hôte sont inchangés, octet pour octet. Le pair
`externe` reste sauté par resoudre_flux.

Non déclaré volontairement : le rôle `chrony` n'est référencé par aucun
groupe ni playbook — un flux pour lui aurait été une règle morte.

Preuves : 24 OK, 0 échec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 13:10:45 -04:00
7dc7720b4b client_pki : l'empreinte du root CA n'est pas un secret de voûte
Le rôle dérive l'empreinte à chaud depuis l'autorité, parce qu'un from-zero
régénère l'AC avec une empreinte neuve. Figée en voûte, elle serait périmée
dès la première reconstruction — et une empreinte périmée fait échouer le
bootstrap de chaque hôte.

Or `defaults/main.yml` portait encore un défaut lisant
`vault_step_ca_fingerprint`. Ce défaut était mort : la tâche suivante écrase
le fait sans condition, donc la valeur de la voûte n'avait aucun effet.

Le recensement de voute.py s'y laissait prendre — il cherche la chaîne
`vault_*` sans pouvoir savoir qu'un défaut n'est jamais lu. La « source
unique » avait hérité de l'erreur, et le panneau réclamait un secret
impossible à fournir avant que l'AC n'existe.

Défaut retiré : la clé disparaît du recensement (25 -> 24 exigés), du
gabarit et du panneau. `client_pki_ca_fingerprint_override` reste le moyen
d'épingler une empreinte.

`docs/intrants-communs.md` §H était une troisième copie manuelle de la liste
des secrets, avec les deux mêmes erreurs ; elle renvoie maintenant à
`voute.py lister` et à la preuve P18.

Preuves : 24 OK, 0 échec ; voute.py verifier --strict passe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 22:37:27 -04:00
4a1d0836d0 docs : un README par rôle (12 manquants) + carte remise à l'état du code
Solde la dette documentaire repérée à l'audit du 2026-07-03. Tous les rôles ont
désormais un README, au format maison (intention → rôle → variables →
notes/limites → prérequis). Ils documentent ce qui ne se lit PAS dans les tâches :

- serveur_debian : rôle-catégorie sans tâches — il ne porte que le flux SSH du
  plan de gestion, sans quoi les nftables générés couperaient l'accès Ansible à
  toute la flotte ; le vrai travail est dans le playbook de groupe.
- hosts_statiques : le plancher /etc/hosts comme CONDITION de l'ordre de
  reconstruction (résolution par nom avant que PowerDNS existe) ; fichier
  entièrement géré, alias d'exposition best-effort.
- resoudre_base / resoudre_annuaire : entrées, sorties (facts), et pourquoi le
  FQDN plutôt que le nom court (fédération + nom canonique pour verify-full).
- serveur_dovecot : les 3 réglages Dovecot 2.4 qui conditionnent la remise
  (static_allow_all_users, mail_inbox_path vide, mail_path par utilisateur) et
  le local-part seul comme chemin commun LMTP/IMAP ; limite mono-domaine.
- serveur_postfix : liens mailstore/milter, recopie de /etc/hosts dans le chroot.
- serveur_rspamd : clé DKIM idempotente ; le domaine signé doit être PUBLIC en
  prod, sinon la signature ne vaut rien pour les MX distants.
- client_backup / serveur_backup : jobs déclaratifs, chiffrement côté client,
  deux pièges (jobs vides = silencieux ; dépôt neuf vide après un from-zero) ;
  hors-nœud n'est pas hors-site.
- serveur_oauth2_proxy : le patron réutilisable Keycloak-devant-n'importe-quoi,
  et pourquoi allow_unverified_email est nécessaire avec un annuaire LDAP.
- serveur_icingaweb2 : modes ldap vs external, écoute à restreindre en SSO.
- client_unbound : le garde-fou de bascule (apply ET confirm, validation réelle
  avant de toucher /etc/resolv.conf).

docs/carte-set-ops.md ne décrivait plus l'état du code :
- expose EST consommé au déploiement (annoncé comme « Phase 3 à venir ») :
  expositions_des_applications + expositions.conf.j2, alias /etc/hosts, SANs
  d'edge dérivés par instancier.py ;
- 3 mécanismes transverses ajoutés (résolution d'annuaire, plancher de
  résolution, resoudre_base nommé dans les bindings app→base) ;
- 5 entrées d'index ajoutées : réseau/pare-feu, ordre de déploiement,
  preuve/recette, exploitation courante, wiki — pans du corpus non indexés ;
- ce qui reste ouvert est marqué : meta/liens.yml sur le seul serveur_postfix,
  requiert non consommé au déploiement (l'ordre vient des couches + du graphe).

make verifier vert : ansible-lint 514 fichiers, tests, syntax-check,
23 preuves CONFORME / 0 échec / 0 sauté.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 11:32:06 -04:00
131a30075d Aurore : opacites reduites ; etat de forge-01 elucide
Palette (promo/, les 4 pages) — les teintes rose, verte et violette etaient
trop presentes une fois etalees au tour precedent. Reduction d'environ 40 % :
rose .075 -> .044, vert .058 -> .034, violet .062 -> .036, arrets
intermediaires abaisses dans la meme proportion, opacite de la nappe .aurora
.44 -> .32. Geometrie inchangee : meme largeur, meme position, seule
l'intensite baisse. 0 conflit CSS entre les quatre pages.

forge-01 — la note precedente presentait comme une contradiction a trancher
le fait que le plan dise `etat: planifie` et que le CHANGELOG du 2026-07-03
dise le branding « prouve sur forge-01 ». Ce n'en etait pas une : l'hote a ete
cree, puis supprime. Le plan decrit l'etat COURANT, le CHANGELOG un etat PASSE
— les deux disent vrai. La preuve reste valable, simplement non rejouable tant
que l'hote n'est pas recree. roles/serveur_forgejo/README.md corrige.

Valide : equilibre accolades/parentheses du CSS, 0 conflit CSS,
prouver.py --verifier -> CONFORME 16/16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 11:57:50 -04:00
f8e754c0b0 Aurore rose-mauve et vert fluo ; thème Forgejo étendu au wiki
Palette (promo/, les 4 pages) — ajoute --rose:#ff6fc4 (frange magenta) et
--vert:#5cff9d (vert fluo), uniquement dans les DÉGRADÉS FONCÉS : fond fixe du
corps, nappe .aurora dérivante, filets .rule. Opacités de 5,5 % à 8,5 %, une
teinte et non un motif. Le vert entre par la gauche et le rose sort par la
droite, comme une vraie aurore. --aurora (texte et boutons) est inchangé :
l'identité de marque ne bouge pas. Appliqué identiquement aux quatre fichiers,
0 conflit CSS après coup.

Thème Forgejo (alliance.css, 29 → 118 lignes) — la feuille étant injectée par
templates/custom/header.tmpl sur toutes les pages, le wiki est couvert sans
feuille distincte. Réorganisée en deux sections de risque explicite :
  §1 Variables — couleurs officielles + ciel nocturne. Sûr, résiste aux
     mises à jour. ÉPROUVÉ sur forge-01 (CHANGELOG 2026-07-03).
  §2 Décor — fond aurore, filet sous les titres, citations, tableaux et code
     en ligne de .markup. Fragile (classes internes). JAMAIS RENDU.
Supprimer §2 ramène au thème sobre. Le ciel nocturne ne s'applique qu'aux
thèmes sombres, pour ne pas casser le thème clair.

docs/theme-forgejo-hors-flotte.md — pose manuelle sur une instance Forgejo non
gérée par Set-OPS (la forge historique qui héberge ce dépôt et son wiki).
Forgejo n'ayant aucun réglage web pour le CSS, il faut déposer la feuille sur
le serveur. La procédure ne duplique aucun fichier : elle pointe vers ceux du
rôle. Inclut la détection du répertoire custom, un garde-fou pour ne pas
écraser un header.tmpl existant (ajout de ligne), la vérification curl et la
marche arrière.

Corrige deux affirmations fausses de ma part :
- « thème jamais rendu par un Forgejo réel » était faux pour §1, éprouvée.
  Le statut est désormais donné section par section.
- Contradiction consignée sur forge-01 : le plan la dit `etat: planifie`, le
  CHANGELOG 2026-07-03 dit le branding « prouvé sur forge-01 ». Les deux ne
  peuvent pas être vrais ; l'écart est écrit dans le README du rôle, à trancher.

Validé : équilibre accolades/parenthèses du CSS, 0 conflit CSS entre les quatre
pages, lien de doc résolu, ansible-lint 0 échec sur 485 fichiers,
prouver.py --verifier → CONFORME 16/16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:42:52 -04:00
72a5fdbbc4 Portabilité from-zero : 4 correctifs débusqués par la reconstruction
Reconstruction complète prouvée (14 VM + sauvegardes + AC supprimées, puis
`make myDay` rebâtit tout ; `make valider` entièrement vert). Bugs corrigés :

- client_pki : empreinte du root CA dérivée dynamiquement de l'autorité
  (au lieu d'une valeur figée en Vault) — une AC régénérée a une empreinte neuve.
- serveur_keycloak : assignation de rôle tolérante aux utilisateurs absents
  (annuaire vide sur un from-zero).
- serveur_prometheus : ne scrute que les hôtes ACTIFS (client_metrique ∩ hotes_actifs).
- nftables résolu : compatible Docker — remplace la seule table setops_flux (pas de
  flush ruleset, préserve les tables Docker) + forward autorise docker0/established.
  Sans ça, forward policy drop coupait Collabora (conteneur).

Ajoute playbooks/proxmox/supprimer_vm_debian.yml (suppression par VMID, garde-fous).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 06:45:12 -04:00
f05f505b88 Reconstruction propre : orchestrateur ordonné, registre des flux + pare-feu
Rend l'écosystème reconstructible en une commande (create+deploy idempotent) et
ajoute la couche « accès » (nftables least-privilege) au zéro-confiance.

Orchestrateur (phase 2) :
- docs/couches-deploiement.yml : registre des couches (socle → pki → services → apps → agents)
- scripts/orchestrer.py : tri par couche + topo intra-couche (graphe) → playbooks/site.yml ordonné
- Makefile : site / deployer-tout / flotte-creer / reconstruire / myDay (+ gardes CONFIRMER)
- docs/dependances-groupes.yml : graphe complété (keycloak→openldap, dovecot, postfix, icingaweb2, nextcloud)

Audit codé-en-dur (phase 1b) : labels/slug OIDC dérivés de l'intrant `organisation`
(serveur_forgejo/grafana/nextcloud) — le moteur ne porte plus de nom de tenant.

Registre des flux réseau (phase 0) :
- meta/flux.yml pour tous les rôles (29 rôles, 63 flux ; schéma + matrice validés)
- scripts/resoudre_flux.py : matrice d'audit (docs/registre-flux.md) + rulesets nftables résolus par hôte
- roles/nftables_baseline : déploie le ruleset résolu (moindre-privilège) quand activé, sinon repli

Correctifs : détection du coffre Vault (chemin production → inventaire réellement résolu).
Outillage : make wiki-publier (publication du wiki pédagogique dans Forgejo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 03:08:09 -04:00
af13fc37c9 Rôles web : renommer web_frontal/dorsal -> serveur_web_frontal/dorsal (convention)
Aligne sur la convention serveur_* de tous les rôles de service (vars incluses).
Le modèle presence-web (pré-existant) utilisait déjà ces noms -> il colle enfin.
Aussi : serveur_web_dorsal ne chown plus le code (reste root, lecture seule pour
l'app = plus sûr + git idempotent, fini le 'propriété douteuse' au pull).
Idempotence prouvée (2e passage changed=0). lint production.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:49:31 -04:00
c8ccbb5e97 client_pki : ré-émettre le cert sur dérive de SAN (fin du rm manuel)
Le garde 'creates:' sautait la ré-émission même quand les SAN voulus changeaient
(ex: nouvelle exposition -> client_pki_sans mis à jour). Contourné 3× à la main ce
soir. Corrigé : on lit les SAN du cert existant (openssl) et on ré-émet si le cert
est absent OU si un SAN voulu manque, puis on recharge les consommateurs
(client_pki_reload_services). Idempotent (SAN à jour -> sauté) + dérive détectée,
prouvés. ansible-lint production : 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:43:23 -04:00
eee7dbd108 web_dorsal : rôle webapps dynamiques (2e brique plateforme, 100% natif)
Runtime (venv/paquets) + service systemd + nginx local, derrière l'edge, ZÉRO
conteneur. Chaque app : git clone (depth 1) → venv + pip → unité systemd durcie
(app en localhost, user webapp, NoNewPrivileges/PrivateTmp) → vhost nginx local
qui la fronte. data_dir persistant pour l'état local (SQLite) ; BD réseau via
resoudre_base sinon.

Éprouvé bout-en-bout sur web-frontal-01 par le rôle : monregistraire (FastAPI+
uvicorn+SQLite) cloné de Forgejo, servi via l'edge en HTTPS step-ca, API OK.
Avec web_frontal, la plateforme webapp native est complète. lint production: 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:37:47 -04:00
72b7579c6e web_frontal : git clone superficiel (depth 1) + git installé + fix nom de tâche
Chemin git prouvé de bout en bout : le rôle clone le site depuis le Forgejo
souverain (depth 1 — pas l'historique lourd) et le sert derrière l'edge.
Éprouvé sur web-frontal-01 (Alliance Boréale servi via edge depuis /srv/web,
cloné de Forgejo). ansible-lint production : 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:22:11 -04:00
f453f77268 web_frontal : rôle sites statiques (1re brique plateforme webapp, 100% natif)
nginx + sites statiques depuis un dépôt git souverain, derrière l'edge. Pas de
conteneur, pas de runtime, pas de BD — l'éthos libre/natif. Chaque site :
git clone → vhost (headers sécurité + CSP self-only, gzip, try_files). SAN edge
+ plancher auto-dérivés. meta/flux.yml (ingress 80 depuis edge).

Spike prouvé sur web-frontal-01 (site Alliance Boréale servi via edge, HTTPS
step-ca vérifié, CSP en place). NB : le chemin git-clone est codifié mais le
spike a servi via copie locale → à éprouver (pousser le site en Forgejo).
Les apps dynamiques (dorsal systemd) = rôle à venir. ansible-lint production: 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:12:19 -04:00
0d2bc2c2e9 Machinerie d'exposition : SAN auto-dérivés + vhost WebSocket-aware
Élimine 3 gotchas du spike Nextcloud/Collabora :
- instancier dérive sans_exposition par edge (FQDN exposés du plan) → le cert edge
  (client_pki_sans) se met à jour tout seul, plus de liste manuelle en group_vars ;
- champ 'websocket' sur une application → le vhost d'exposition ajoute la map
  $http_upgrade + en-têtes Upgrade/Connection + timeout long (Collabora, éditeurs live) ;
- (plancher /etc/hosts : le mécanisme d'alias existait déjà, publier_expositions=true).

Prouvé sur le lab : cert edge couvre les 6 expositions, bureau 200 en WebSocket.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:30:22 -04:00
da4976aa98 serveur_collabora : rôle codifié depuis le spike (Docker, WOPI)
Conteneur collabora/code (restart always), publié sur localhost + IP LAN
(gotcha : bind localhost seul → 502 depuis l'edge), aliasgroup = domaine
Nextcloud, ssl.termination à l'edge, attente santé /hosting/capabilities.
meta/flux.yml (ingress 9980 depuis edge) alimente le registre des flux.

Docker (prouvé au spike) ; CODE natif = option future à spiker. Tag à épingler.
ansible-lint profil production : 0 échec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:03:07 -04:00
79d0529686 serveur_nextcloud : rôle codifié depuis le spike (13 gotchas encodés)
Maison numérique souveraine (offres maison-obnl + cabinet). Tâches découpées :
paquets → php (512M + TLS PG) → install (occ, PG verify-full) → exposition
(vhost .mjs) → configure (overwrite/redis/anti-SSRF/import CA) → OIDC user_oidc
→ Collabora richdocuments (WOPI split URL) → thème Alliance (ciel étoilé).

Chaque réglage encode un piège prouvé sur collab-01 : imagick Debian 13,
PGSSLMODE dans php-fpm, MIME .mjs, allow_local_remote_servers (SSRF), import
root step-ca, memory_limit 512M, public_wopi_url après activate-config…

Version épinglée (robustesse). ansible-lint profil production : 0 échec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:00:39 -04:00
c98bc8393f Registre des flux : schéma meta/flux.yml + pilote (postgresql/metrique/nginx)
Couche accès du zéro-confiance (complète le chiffrement). Chaque rôle possède
ses flux (comme meta/empreinte) : sens/port/protocole/pair/chiffrement/raison.
Résolu par le plan (pair→IP) → génère les règles nftables (default deny) ET le
registre d'audit. Doc de conception + 3 pilotes validés (résolveur-démo).

Séquence : schéma+pilote (fait) → résolveur → remplir les rôles → générer +
registre → activer nftables prudemment (action destructive).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 09:41:42 -04:00
04d807c78d Module Collaboration : squelette serveur_nextcloud (defaults/meta/README)
Cœur de l'offre maison-obnl (OBNL/coops). Surface de config posée (BD via
resoudre_base, Redis, OIDC Keycloak, Collabora WOPI, exposé cloud.<domaine>,
sauvegarde Tier 1). tasks/main.yml VOLONTAIREMENT absent : attend le spike
empirique (éprouver l'outil avant le rôle) — déployer + prouver OIDC/WOPI/
fichiers sur un vrai nœud avant de codifier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 01:39:11 -04:00
867e2f8cdc Découplage instance : realm centralisé + vars génériques
Fin des dernières poches de 'codé en dur' liées à l'instance d'origine :
- realm SSO centralisé sur l'intrant identite_realm (defaut chezlepro,
  retro-compatible) ; les 4 rôles (keycloak/forgejo/grafana/oauth2_proxy)
  en dérivent. Expose dans la GUI (panneau Intrants).
- vars brandees renommees generiques : chezlepro_timezone -> fuseau_horaire,
  chezlepro_organisation -> organisation (chrony, openldap, GUI, docs).

Aucune reference fonctionnelle aux anciens noms. Le moteur ne porte plus le
nom d'un tenant. Technolibre a son propre realm (technolibre).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 00:47:44 -04:00
5108a6c04d 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 — data-sql-01 existe chez plusieurs tenants — + nom canonique TLS).

- resoudre_base : db_host renvoie le FQDN (hote.domaine_interne) → propagé à
  keycloak/forgejo/icinga (ils en dérivent tous). Résolu par le plancher.
- client_journal : loki_url dérivé du groupe serveur_loki en FQDN (fin du
  10.0.14.11 périmé).
- nettoyage des defaults db_host IP morts (10.0.13.11, écrasés par resoudre_base).

Aucune IP littérale ne subsiste dans les defaults des rôles. verify-full OK
(SAN FQDN). S'applique au prochain déploiement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 00:40:57 -04:00
20329895f7 Zéro-confiance : flux Courriel chiffré (LMTP verify + client_smtp STARTTLS)
Deux sauts est-ouest en clair, corrigés :
- LMTP edge-mta -> infra-mail:24 : lmtp_tls_security_level=verify +
  lmtp_tls_CAfile=root_ca (le CONTENU des courriels). Dovecot LMTP offrait
  déjà STARTTLS (cert step-ca, SAN infra-mail-01) : aucun changement Dovecot.
- client_smtp -> edge-mta:25 : msmtp tls_starttls on + tls_trust_file root_ca.

Prouvé : livraison LMTP status=sent (verify ⇒ TLS obligatoire), msmtp tls=on
smtpstatus=250. Déjà chiffrés : submission :587, Postfix->LDAP ldaps, IMAP :993.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:47:29 -04:00