les depots tiers passent par le cache, et le tenant n a plus le sien

Deux mouvements d une seule doctrine : le site fournit tout ce dont un
tenant a besoin pour venir au monde.

1. LES DEPOTS TIERS. Grafana, Icinga, Smallstep et Collabora ne publient
qu en HTTPS ; apt-cacher-ng ne relaie pas un tunnel, et le socle pose donc
Acquire::https::Proxy DIRECT. Chaque machine sortait elle-meme sur
Internet. Le cache declare desormais un Remap par fournisseur, et chaque
role demande en {{ ..._depot_schema }}:// - http des qu un cache est
declare, https sinon. Le TLS n est rompu nulle part : il est TERMINE au
cache, qui est notre machine, et l integrite vient des signatures.

  avant : 4 fournisseurs en HTTPS direct, 14 machines sortant seules
  apres : 0 source en HTTPS direct, 0 erreur apt sur 14 machines

Trois lecons. Le remap appartient au cache qui SORT : pose sur un cache
chaine, il tente le HTTPS a travers son amont et rend 503. apt_repository
AJOUTE au lieu de remplacer, donc l ancienne ligne https sortait toujours.
Et la liste des fournisseurs ne se devine pas - j en avais trois, l audit
en a revele un quatrieme.

2. LE CACHE DU TENANT. Sa ligne portait son propre retrait depuis toujours
- service MUTUALISABLE, un ecosysteme au premier age peut pointer sur celui
de son hote. Retiree. Ce qu on perd, dit franchement : plus de trafic
inter-zone et plus de charge sur site-cache-01, contre un service de moins
a poser, superviser et reproduire.

3. UN ROLE QU ON RETIRE DOIT DEFAIRE CE QU IL A FAIT. Le retrait a montre
que rien ne nettoie derriere. Le fichier apt visait un cache eteint en
ecrasant le plancher qui fonctionnait - le defaut deja paye a quinze
machines. Et les sondes du cache restaient, le porteur poussant pour des
services qu Icinga ne definit plus (404). Le socle retire le premier,
client_sante derive les sondes attendues et retire les orphelines.

P65 refuse tout role visant un depot relaye en https ecrit en dur. Sa
limite est dite : elle empeche une regression sur ce qui est connu, elle ne
decouvre pas l inconnu.

Mesure : Icinga 87 OK sur 96, prouver 65 OK, lint 0 defaut.

Reste, et c est dit : apt-cacher-ng tourne toujours sur forge-01 que plus
aucun plan ne declare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-10 14:53:48 -04:00
parent 9685e0bdbe
commit c6a7150c60
20 changed files with 488 additions and 23 deletions

View file

@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
## Écrire, puis relire (D-68) ## Écrire, puis relire (D-68)
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**. `--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
C'est aussi ce que font les 64 preuves de `make prouver` : elles lisent le dépôt, sans le C'est aussi ce que font les 65 preuves de `make prouver` : elles lisent le dépôt, sans le
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
dépôt annonce.** dépôt annonce.**

View file

@ -1,5 +1,100 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-09-10 (10) — Plus rien ne sort chercher ses paquets, et un tenant de moins a nourrir
Deux mouvements d'une seule doctrine : **le site fournit tout ce dont un tenant a besoin
pour venir au monde.**
### 1. Les depots tiers passent enfin par le cache
Set-OPS tire ses paquets applicatifs de fournisseurs qui ne publient **qu'en HTTPS** —
Grafana, Icinga, Smallstep, Collabora. `apt-cacher-ng` ne relaie pas un tunnel, et le socle
pose donc deliberement `Acquire::https::Proxy "DIRECT"` : chaque machine sortait
elle-meme sur Internet. Mesure du matin : 58 references de depot, quatorze machines sortant
chacune de son cote.
Le remede tient en deux moities, et une seule ne sert a rien :
- le cache **DECLARE** un `Remap-*` par fournisseur — le client demande en `http://`, le
cache va chercher en `https://` ;
- chaque role **DEMANDE** en `{{ ..._depot_schema }}://`, qui vaut `http` des qu'un cache
d'amorcage est declare, `https` sinon. *Degrader, jamais deviner.*
Le TLS n'est rompu nulle part : il est **termine au cache**, qui est notre machine. Et
l'integrite ne vient pas du transport mais des signatures du depot — le raisonnement deja
tenu pour `deb.debian.org` depuis toujours.
**Trois choses que la mesure a apprises :**
**Le remap appartient au cache qui SORT.** Pose aussi sur le cache du tenant — chaine vers
celui du site — il tentait le HTTPS *a travers* son amont, ce qui exige un `CONNECT` que
l'amont ne fait pas :
remap sur le cache du tenant (chaine) -> 503
remap sur le seul cache du site -> 200
Un cache qui relaie n'a rien a remapper : il passe la demande a qui sort. Meme forme que la
filiation elle-meme.
**`apt_repository` AJOUTE, il ne remplace pas.** Passer une source de `https` a `http` y
ecrivait une SECONDE ligne ; apt interrogeait les deux, et l'ancienne sortait toujours.
Le symptome le disait sur les quatorze machines : *« La cible Packages est specifiee
plusieurs fois dans grafana.list:1 et :2 »*.
**La liste des fournisseurs ne se devine pas.** J'en avais recense trois ; l'audit des
sources en a revele un quatrieme — Collabora, sur une seule machine. **P65** refuse
desormais tout role visant un depot RELAYE en `https://` ecrit en dur. Sa limite est dite :
elle empeche une regression sur ce qui est connu, elle ne decouvre pas l'inconnu.
avant : 4 fournisseurs en HTTPS direct, 14 machines sortant seules
apres : 0 source en HTTPS direct, 0 erreur apt sur 14 machines
### 2. Le cache d'artefacts quitte le plan du tenant
La ligne portait son propre retrait depuis toujours :
> *« c'est un service MUTUALISABLE — un ecosysteme au premier age peut aussi bien pointer
> sur celui de son hote »*
Retiree. `client_artefacts` le voit tout seul : sans hote portant `serveur_artefacts`, il
n'ecrit rien et laisse en place le plancher d'amorcage qui vise `site-cache-01`.
**Ce qu'on perd, dit franchement** : les quatorze machines interrogent desormais le cache du
SITE a travers la frontiere, au lieu d'un cache local a la zone. Plus de trafic inter-zone,
plus de charge sur `site-cache-01` — contre un service de moins a poser, superviser et
reproduire dans chaque ecosysteme.
### 3. Un role qu'on retire doit pouvoir DEFAIRE ce qu'il a fait
Retirer le role a revele que rien ne nettoie derriere lui. Deux fois :
- **le fichier apt** `00-setops-artefacts` continuait de viser un cache eteint, en ecrasant
le plancher qui, lui, fonctionnait — exactement le defaut deja paye (« quinze machines ont
perdu apt d'un coup »). Le SOCLE le retire desormais, parce qu'il est le seul a tourner
dans les deux cas ;
- **les sondes** `cache-apt` et `cache-apt-volume` restaient sur la forge, et le porteur
poussait pour des services qu'Icinga ne definit plus :
ECHEC du rapport Icinga pour « cache-apt » : {"error":404,"status":"No objects found."}
`client_sante` derive maintenant les sondes ATTENDUES sur chaque hote — ses groupes
croises avec les `meta/supervision.yml` — et retire celles dont plus aucun groupe ne
repond. Meme derivation que celle de `serveur_icinga`, du cote du porteur.
### Etat mesure
sources apt en HTTPS direct 0 / 14 machines
erreurs `apt-get update` 0 / 14 machines
mandataire site-cache-01 seul, partout
Icinga 87 OK | 9 UNKNOWN sur 96 services
(les 9 = `sauvegarde`, minuteur nocturne)
prouver 65 OK, 0 echec
ansible-lint 0 defaut
**Reste, et c'est dit** : `apt-cacher-ng` tourne toujours sur `forge-01`, que plus aucun
plan ne declare. La prochaine reconstruction ne l'installera pas ; sur la machine actuelle,
il subsiste. Un service qu'aucun plan ne reclame est une derive, meme benigne.
## 2026-09-10 (9) — La reconstruction de confirmation : 0 echec, 34 minutes ## 2026-09-10 (9) — La reconstruction de confirmation : 0 echec, 34 minutes
Seconde reconstruction complete de Chezlepro dans la journee, cette fois pour EPROUVER les Seconde reconstruction complete de Chezlepro dans la journee, cette fois pour EPROUVER les

View file

@ -7,7 +7,7 @@
> [`docs/audit/affirmations.md`](affirmations.md). > [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml` - **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
- **Verdict** : ✅ CONFORME (64 OK · 0 echec · 0 saute) - **Verdict** : ✅ CONFORME (65 OK · 0 echec · 0 saute)
## Preuves ## Preuves
@ -28,7 +28,7 @@
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. | | P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. | | P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 36 groupes (inventaire dechiffre et parse). | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 34 groupes (inventaire dechiffre et parse). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) | | P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
@ -37,15 +37,15 @@
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). | | P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. | | P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. | | P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 82 regle(s). | | P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 81 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). | | P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. | | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 58 scripts expliques et atteignables, 115 cibles make documentees, 67 roles avec README. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 58 scripts expliques et atteignables, 115 cibles make documentees, 67 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 39 exigence(s) de role, toutes satisfaites (132 cle(s) declaree(s) par l'instance). | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (132 cle(s) declaree(s) par l'instance). |
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (36 genere(s) exempte(s)). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (36 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
@ -62,14 +62,14 @@
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). | | P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. | | P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (121 lignes). | | P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (121 lignes). |
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 216 regles `pass`), tous non consignes et tous motives. | | P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 213 regles `pass`), tous non consignes et tous motives. |
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 | | P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. | | P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | | P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. | | P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. | | P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. | | P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (64 preuves, 67 roles, 41 groupes). | | P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (65 preuves, 67 roles, 41 groupes). |
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. | | P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). | | P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `2d9dc86` (publie le 2026-09-09). | | P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `2d9dc86` (publie le 2026-09-09). |
@ -77,6 +77,7 @@
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). | | P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s | | P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 22 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ | | P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 22 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
## Couverture des affirmations ✅ du registre ## Couverture des affirmations ✅ du registre

View file

@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
## Le trou qu'il comble ## Le trou qu'il comble
`scripts/prouver.py` porte 64 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le `scripts/prouver.py` porte 65 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
propriétaire, que rien n'est codé en dur. propriétaire, que rien n'est codé en dur.

View file

@ -79,6 +79,24 @@
- artefacts_amorcage is defined - artefacts_amorcage is defined
- artefacts_amorcage | string | length > 0 - artefacts_amorcage | string | length > 0
# RETIRER CE QUI DESIGNE UN CACHE QUI N'EXISTE PLUS (2026-09-10).
#
# `client_artefacts` ecrit `00-setops-artefacts`, qui trie APRES le plancher et gagne.
# Quand l'ecosysteme cesse de declarer `serveur_artefacts` — parce qu'il s'en remet au
# cache du SITE — l'hote quitte le groupe, le role ne tourne plus, et PERSONNE ne
# retire son fichier. Il continuerait donc de viser un cache eteint, en ecrasant le
# plancher qui, lui, fonctionne.
#
# C'est le defaut deja paye : « quinze machines ont perdu apt d'un coup, et le
# deploiement ne pouvait plus atteindre la couche qui aurait repare le cache ». Un
# role qu'on retire doit pouvoir defaire ce qu'il a fait — ici, depuis le socle, qui
# est le seul a tourner dans les deux cas.
- name: Retirer la source d'artefacts de l'ecosysteme quand il n'en declare plus
ansible.builtin.file:
path: /etc/apt/apt.conf.d/00-setops-artefacts
state: absent
when: (groups['serveur_artefacts'] | default([])) | length == 0
- name: Lire le résolveur en place - name: Lire le résolveur en place
ansible.builtin.slurp: ansible.builtin.slurp:
src: /etc/resolv.conf src: /etc/resolv.conf

View file

@ -11,9 +11,9 @@ client_journal_config: "/etc/alloy/config.alloy"
client_journal_loki_url: "http://{{ (groups.get('serveur_loki') or ['obs-01']) | first }}.{{ domaine_interne }}:3100/loki/api/v1/push" client_journal_loki_url: "http://{{ (groups.get('serveur_loki') or ['obs-01']) | first }}.{{ domaine_interne }}:3100/loki/api/v1/push"
# Depot apt officiel Grafana (partage avec serveur_loki / serveur_grafana). # Depot apt officiel Grafana (partage avec serveur_loki / serveur_grafana).
client_journal_depot_cle_url: "https://apt.grafana.com/gpg-full.key" client_journal_depot_cle_url: "{{ client_journal_depot_schema }}://apt.grafana.com/gpg-full.key"
client_journal_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" client_journal_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc"
client_journal_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" client_journal_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] {{ client_journal_depot_schema }}://apt.grafana.com stable main"
# --- TLS (zero-confiance) : Alloy pousse vers Loki en https + verifie root_ca --- # --- TLS (zero-confiance) : Alloy pousse vers Loki en https + verifie root_ca ---
client_journal_loki_tls: false client_journal_loki_tls: false
@ -41,3 +41,19 @@ client_journal_sonde_url: >-
http://{{ client_journal_http_adresse }}:{{ client_journal_http_port }} http://{{ client_journal_http_adresse }}:{{ client_journal_http_port }}
# La memoire d'un passage a l'autre : sans elle, un compteur cumulatif ne dit pas QUAND. # La memoire d'un passage a l'autre : sans elle, un compteur cumulatif ne dit pas QUAND.
client_journal_sonde_etat: "/var/lib/setops/journaux-jetees" client_journal_sonde_etat: "/var/lib/setops/journaux-jetees"
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
client_journal_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -30,6 +30,24 @@
group: root group: root
mode: "0644" mode: "0644"
# `apt_repository` AJOUTE, IL NE REMPLACE PAS (mesure du 2026-09-10).
#
# Passer une source de `https://` a `http://` y ecrit une SECONDE ligne au lieu de
# corriger la premiere. apt interroge alors les deux — et l'ancienne continue de sortir
# en direct sur Internet, ce que le passage au cache visait justement a supprimer. Le
# symptome le disait, sur les quatorze machines :
#
# W: La cible Packages est specifiee plusieurs fois dans grafana.list:1 et :2
#
# On retire donc explicitement la forme precedente. `state: absent` ne mord que si la
# ligne existe : sur une machine neuve, cette tache ne fait rien.
- name: Retirer la forme HTTPS du depot (elle contournait le cache)
ansible.builtin.apt_repository:
repo: "{{ client_journal_depot_source | replace('http://', 'https://') }}"
filename: grafana
state: absent
when: client_journal_depot_schema == 'http'
- name: Ajouter le depot apt Grafana - name: Ajouter le depot apt Grafana
ansible.builtin.apt_repository: ansible.builtin.apt_repository:
repo: "{{ client_journal_depot_source }}" repo: "{{ client_journal_depot_source }}"

View file

@ -65,9 +65,9 @@ client_pki_ca_fingerprint: ""
client_pki_provisioner_password: "{{ vault_step_ca_provisioner_password | default('') }}" # rempli depuis la voute (vault_step_ca_provisioner_password) client_pki_provisioner_password: "{{ vault_step_ca_provisioner_password | default('') }}" # rempli depuis la voute (vault_step_ca_provisioner_password)
# Depot apt officiel Smallstep (partage avec serveur_step_ca). # Depot apt officiel Smallstep (partage avec serveur_step_ca).
client_pki_depot_cle_url: "https://packages.smallstep.com/keys/apt/repo-signing-key.gpg" client_pki_depot_cle_url: "{{ client_pki_depot_schema }}://packages.smallstep.com/keys/apt/repo-signing-key.gpg"
client_pki_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc" client_pki_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc"
client_pki_depot_uri: "https://packages.smallstep.com/stable/debian" client_pki_depot_uri: "{{ client_pki_depot_schema }}://packages.smallstep.com/stable/debian"
client_pki_depot_suite: "debs" client_pki_depot_suite: "debs"
# QUI PEUT LIRE LA CLE PRIVEE (2026-08-25). # QUI PEUT LIRE LA CLE PRIVEE (2026-08-25).
@ -127,3 +127,19 @@ client_pki_sonde_crit_h: 3
# Port local ou comparer le certificat SERVI a celui du disque. Vide = pas de comparaison # Port local ou comparer le certificat SERVI a celui du disque. Vide = pas de comparaison
# (l'hote n'expose rien en TLS, ou son consommateur n'ecoute pas en local). # (l'hote n'expose rien en TLS, ou son consommateur n'ecoute pas en local).
client_pki_sonde_port: "" client_pki_sonde_port: ""
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
client_pki_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -72,6 +72,75 @@
name: curl name: curl
state: present state: present
# --- UN ROLE QU'ON RETIRE DOIT POUVOIR DEFAIRE CE QU'IL A FAIT (2026-09-10) -----------
#
# Chaque role depose SES sondes dans `/usr/local/lib/setops/sondes/`. Quand un ecosysteme
# cesse de declarer un service — `serveur_artefacts` retire du plan le 2026-09-10, parce
# que le SITE fournit les paquets — l'hote quitte le groupe, le role ne tourne plus, et
# PERSONNE ne retire ses sondes. Le porteur continue alors de les executer et de pousser
# un resultat pour un service qu'Icinga ne definit plus :
#
# ECHEC du rapport Icinga pour « cache-apt » : {"error":404,"status":"No objects found."}
#
# Ce n'est pas qu'un bruit : une sonde orpheline consomme le delai du porteur a chaque
# passage, et son echec ressemble a celui d'une vraie sonde en panne.
#
# CE QUI EST ATTENDU SE DERIVE, comme chez `serveur_icinga` : les `meta/supervision.yml`
# des roles, croises avec les GROUPES de cet hote. On ne retire donc jamais la sonde d'un
# role qui s'applique — seulement celles dont plus aucun groupe ne repond.
- name: Relever les sondes que les roles declarent
ansible.builtin.find:
paths: "{{ role_path }}/.."
patterns: supervision.yml
recurse: true
depth: 3
delegate_to: localhost
become: false
run_once: true
register: client_sante_metas
- name: Lire chaque declaration de supervision
ansible.builtin.slurp:
src: "{{ item.path }}"
delegate_to: localhost
become: false
run_once: true
loop: "{{ client_sante_metas.files }}"
loop_control:
label: "{{ item.path | dirname | dirname | basename }}"
register: client_sante_declarations
- name: Assembler le registre des sondes (role -> sondes)
ansible.builtin.set_fact:
client_sante_registre: >-
{{ dict(client_sante_declarations.results
| map(attribute='item.path') | map('dirname') | map('dirname') | map('basename')
| zip(client_sante_declarations.results
| map(attribute='content') | map('b64decode') | map('from_yaml')
| map(attribute='sondes'))) }}
run_once: true
- name: Deriver les sondes attendues sur CET hote
ansible.builtin.set_fact:
client_sante_attendues: >-
{{ group_names | map('extract', client_sante_registre) | select('defined')
| select('sequence') | flatten | map(attribute='nom') | list }}
- name: Relever les sondes reellement posees
ansible.builtin.find:
paths: "{{ client_sante_repertoire_sondes | default('/usr/local/lib/setops/sondes') }}"
patterns: "*.sh"
register: client_sante_posees
- name: Retirer les sondes qu'aucun groupe de cet hote ne declare plus
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ client_sante_posees.files }}"
loop_control:
label: "{{ item.path | basename }}"
when: (item.path | basename | regex_replace('\.sh$', '')) not in client_sante_attendues
- name: Déployer le script de rapport de santé - name: Déployer le script de rapport de santé
ansible.builtin.template: ansible.builtin.template:
src: setops-sante.sh.j2 src: setops-sante.sh.j2

View file

@ -73,3 +73,41 @@ serveur_artefacts_sonde_repertoire: /var/cache/apt-cacher-ng
# ecrire. On prend de l'avance. # ecrire. On prend de l'avance.
serveur_artefacts_sonde_pct_avert: 85 serveur_artefacts_sonde_pct_avert: 85
serveur_artefacts_sonde_pct_crit: 93 serveur_artefacts_sonde_pct_crit: 93
# --- LE RELAIS DES DEPOTS TIERS (2026-09-10) -----------------------------------------
#
# Set-OPS tire ses paquets applicatifs de trois fournisseurs qui ne servent QU'EN HTTPS :
# Grafana (grafana, loki, alloy), Icinga (icinga2, icingadb), Smallstep (step-ca). Le
# cache ne relaie pas le HTTPS — il faudrait rompre le TLS de bout en bout — si bien que
# CHAQUE machine allait les chercher elle-meme sur Internet. Mesure du 2026-09-10 : 58
# references de depot sur la flotte, quatorze machines sortant chacune de son cote.
#
# LE REMAP EST LA VOIE PROPRE, et le role `client_artefacts` la nommait deja : le client
# demande `http://apt.grafana.com/...` au cache, le cache va chercher en `https://` chez
# le fournisseur. Le TLS n'est rompu nulle part — il est simplement TERMINE au cache, qui
# est notre machine. Et l'integrite ne depend pas du transport : elle vient des signatures
# du depot, qu'apt verifie de toute facon. C'est exactement le raisonnement deja tenu pour
# `deb.debian.org`, applique aux memes conditions.
#
# `hote` sans schema : le gabarit fabrique la paire `http://<hote>` -> `https://<hote>/`.
serveur_artefacts_remaps:
- { nom: grafana, hote: "apt.grafana.com" }
- { nom: icinga, hote: "packages.icinga.com" }
- { nom: smallstep, hote: "packages.smallstep.com" }
# Collabora — trouve APRES coup, et c'est la lecon : la liste ne se devine pas.
# L'audit des sources apt de la flotte l'a rendu visible quand les trois autres
# etaient deja passes au cache. Une machine sur quatorze le portait.
- { nom: collabora, hote: "www.collaboraoffice.com" }
# LES CLES DE SIGNATURE PASSENT AUSSI, et il a fallu le demander (mesure du 2026-09-10).
#
# apt-cacher-ng ne relaie que les chemins qu'il RECONNAIT comme fichiers de depot. Une
# cle en `.gpg` passait, la meme en `.key` rendait 403 :
#
# packages.smallstep.com/keys/apt/repo-signing-key.gpg 200
# apt.grafana.com/gpg-full.key 403
#
# `PfilePatternEx` ETEND le motif par defaut au lieu de le remplacer — remplacer ferait
# tomber tout le reste. Sans cette ligne, le remap serait vrai a moitie : les paquets
# passeraient par le cache, et chaque machine sortirait quand meme chercher sa cle.
serveur_artefacts_motif_fichiers_sup: '.*\.(key|asc)$'

View file

@ -23,3 +23,30 @@ Offlinemode: 0
# Debian n'est ainsi téléchargé qu'une fois pour tout le site. # Debian n'est ainsi téléchargé qu'une fois pour tout le site.
Proxy: {{ serveur_artefacts_amont }} Proxy: {{ serveur_artefacts_amont }}
{% endif %} {% endif %}
{% if serveur_artefacts_remaps %}
# ETEND le motif des fichiers relayables — il ne le REMPLACE pas. Sans lui, une cle de
# signature en `.key` rend 403 la ou la meme en `.gpg` passe. Vaut pour TOUT cache de la
# chaine : celui qui relaie refuse avant meme de transmettre.
PfilePatternEx: {{ serveur_artefacts_motif_fichiers_sup }}
{% if not serveur_artefacts_amont %}
# --- DEPOTS TIERS : LE REMAP APPARTIENT AU CACHE QUI SORT (mesure du 2026-09-10) ---
#
# Le client demande en `http://`, ce cache va chercher en `https://` chez le fournisseur.
#
# POURQUOI SEULEMENT ICI. Un cache CHAINE (`Proxy:` vers l'amont) qui porte le meme remap
# tente le HTTPS A TRAVERS son amont — ce qui exige un tunnel CONNECT que l'amont ne fait
# pas. Mesure : le cache du tenant rendait alors 503 sur les trois fournisseurs, la ou il
# rendait 200 en se contentant de TRANSMETTRE la requete en clair.
#
# remap sur le cache du tenant (chaine) -> 503
# remap sur le seul cache du site -> 200
#
# Un cache qui relaie n'a rien a remapper : il passe la demande a qui sort. C'est la meme
# forme que la filiation elle-meme — le locataire emprunte, le site va chercher.
{% for r in serveur_artefacts_remaps %}
Remap-{{ r.nom }}: http://{{ r.hote }} ; https://{{ r.hote }}/
{% endfor %}
{% endif %}
{% endif %}

View file

@ -25,8 +25,8 @@ serveur_collabora_port: 9980
# #
# Dépôt PLAT : `Suites: ./` et pas de composant. Une source classique (`deb … stable # Dépôt PLAT : `Suites: ./` et pas de composant. Une source classique (`deb … stable
# main`) ne le trouverait pas. # main`) ne le trouverait pas.
serveur_collabora_depot_uri: "https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb" serveur_collabora_depot_uri: "{{ serveur_collabora_depot_schema }}://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb"
serveur_collabora_depot_cle_url: "https://www.collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg" serveur_collabora_depot_cle_url: "{{ serveur_collabora_depot_schema }}://www.collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg"
serveur_collabora_depot_cle_fichier: "/etc/apt/keyrings/collaboraonline.gpg" serveur_collabora_depot_cle_fichier: "/etc/apt/keyrings/collaboraonline.gpg"
# --- CONFIGURATION : PAR SURCHARGE, PAS PAR RÉÉCRITURE ----------------------- # --- CONFIGURATION : PAR SURCHARGE, PAS PAR RÉÉCRITURE -----------------------
@ -85,3 +85,9 @@ serveur_collabora_domaine_nextcloud: "cloud.{{ domaine_interne }}"
serveur_collabora_admin_actif: false serveur_collabora_admin_actif: false
serveur_collabora_admin_utilisateur: "collabora-admin" serveur_collabora_admin_utilisateur: "collabora-admin"
serveur_collabora_admin_mot_de_passe: "" serveur_collabora_admin_mot_de_passe: ""
# --- SCHEMA DU DEPOT TIERS (2026-09-10) ------------------------------------------------
# Meme derivation que les six autres roles a depot tiers : `http` des qu'un cache est dans
# le chemin, `https` sinon. Voir `serveur_artefacts_remaps` pour la moitie qui relaie.
serveur_collabora_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -47,9 +47,9 @@ serveur_grafana_oidc_scopes: "openid email profile"
serveur_grafana_oidc_role_path: "contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'" serveur_grafana_oidc_role_path: "contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'"
# Depot apt officiel Grafana (partage avec serveur_loki). # Depot apt officiel Grafana (partage avec serveur_loki).
serveur_grafana_depot_cle_url: "https://apt.grafana.com/gpg-full.key" serveur_grafana_depot_cle_url: "{{ serveur_grafana_depot_schema }}://apt.grafana.com/gpg-full.key"
serveur_grafana_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" serveur_grafana_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc"
serveur_grafana_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" serveur_grafana_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] {{ serveur_grafana_depot_schema }}://apt.grafana.com stable main"
# Empreinte du mot de passe admin APPLIQUE — permet la rotation sans lire le secret # Empreinte du mot de passe admin APPLIQUE — permet la rotation sans lire le secret
# en place (impossible : il est hache). Voir tasks/main.yml. # en place (impossible : il est hache). Voir tasks/main.yml.
@ -61,3 +61,19 @@ serveur_grafana_donnees: "/var/lib/grafana"
# --- Sonde de supervision ----------------------------------------------------------- # --- Sonde de supervision -----------------------------------------------------------
serveur_grafana_sonde_url: "http://127.0.0.1:{{ serveur_grafana_port }}" serveur_grafana_sonde_url: "http://127.0.0.1:{{ serveur_grafana_port }}"
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
serveur_grafana_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -62,6 +62,24 @@
group: root group: root
mode: "0644" mode: "0644"
# `apt_repository` AJOUTE, IL NE REMPLACE PAS (mesure du 2026-09-10).
#
# Passer une source de `https://` a `http://` y ecrit une SECONDE ligne au lieu de
# corriger la premiere. apt interroge alors les deux — et l'ancienne continue de sortir
# en direct sur Internet, ce que le passage au cache visait justement a supprimer. Le
# symptome le disait, sur les quatorze machines :
#
# W: La cible Packages est specifiee plusieurs fois dans grafana.list:1 et :2
#
# On retire donc explicitement la forme precedente. `state: absent` ne mord que si la
# ligne existe : sur une machine neuve, cette tache ne fait rien.
- name: Retirer la forme HTTPS du depot (elle contournait le cache)
ansible.builtin.apt_repository:
repo: "{{ serveur_grafana_depot_source | replace('http://', 'https://') }}"
filename: grafana
state: absent
when: serveur_grafana_depot_schema == 'http'
- name: Ajouter le depot apt Grafana - name: Ajouter le depot apt Grafana
ansible.builtin.apt_repository: ansible.builtin.apt_repository:
repo: "{{ serveur_grafana_depot_source }}" repo: "{{ serveur_grafana_depot_source }}"

View file

@ -34,10 +34,10 @@ serveur_icinga_service_redis: "icingadb-redis"
# Depot apt officiel Icinga (paquet trousseau + source signee). # Depot apt officiel Icinga (paquet trousseau + source signee).
serveur_icinga_keyring_url: >- serveur_icinga_keyring_url: >-
https://packages.icinga.com/icinga-archive-keyring_latest+debian{{ ansible_distribution_major_version }}.deb {{ serveur_icinga_depot_schema }}://packages.icinga.com/icinga-archive-keyring_latest+debian{{ ansible_distribution_major_version }}.deb
serveur_icinga_depot_source: >- serveur_icinga_depot_source: >-
deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg]
https://packages.icinga.com/debian icinga-{{ ansible_distribution_release }} main {{ serveur_icinga_depot_schema }}://packages.icinga.com/debian icinga-{{ ansible_distribution_release }} main
# Base Icinga DB : resolue depuis le registre par le groupe consommateur. # Base Icinga DB : resolue depuis le registre par le groupe consommateur.
serveur_icinga_groupe: "serveur_icinga" serveur_icinga_groupe: "serveur_icinga"
@ -138,3 +138,19 @@ serveur_icinga_sonde_config_db: /etc/icingadb/config.yml
# minute de silence est deja anormale. # minute de silence est deja anormale.
serveur_icinga_sonde_age_avert: 60 serveur_icinga_sonde_age_avert: 60
serveur_icinga_sonde_age_crit: 300 serveur_icinga_sonde_age_crit: 300
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
serveur_icinga_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -25,6 +25,24 @@
deb: "/tmp/icinga-archive-keyring.deb" deb: "/tmp/icinga-archive-keyring.deb"
state: present state: present
# `apt_repository` AJOUTE, IL NE REMPLACE PAS (mesure du 2026-09-10).
#
# Passer une source de `https://` a `http://` y ecrit une SECONDE ligne au lieu de
# corriger la premiere. apt interroge alors les deux — et l'ancienne continue de sortir
# en direct sur Internet, ce que le passage au cache visait justement a supprimer. Le
# symptome le disait, sur les quatorze machines :
#
# W: La cible Packages est specifiee plusieurs fois dans grafana.list:1 et :2
#
# On retire donc explicitement la forme precedente. `state: absent` ne mord que si la
# ligne existe : sur une machine neuve, cette tache ne fait rien.
- name: Retirer la forme HTTPS du depot (elle contournait le cache)
ansible.builtin.apt_repository:
repo: "{{ serveur_icinga_depot_source | replace('http://', 'https://') }}"
filename: icinga
state: absent
when: serveur_icinga_depot_schema == 'http'
- name: Ajouter le depot apt Icinga - name: Ajouter le depot apt Icinga
ansible.builtin.apt_repository: ansible.builtin.apt_repository:
repo: "{{ serveur_icinga_depot_source }}" repo: "{{ serveur_icinga_depot_source }}"

View file

@ -9,9 +9,9 @@ serveur_loki_chemin: "/var/lib/loki"
serveur_loki_retention: "744h" # 31 jours serveur_loki_retention: "744h" # 31 jours
# Depot apt officiel Grafana (partage avec serveur_grafana). # Depot apt officiel Grafana (partage avec serveur_grafana).
serveur_loki_depot_cle_url: "https://apt.grafana.com/gpg-full.key" serveur_loki_depot_cle_url: "{{ serveur_loki_depot_schema }}://apt.grafana.com/gpg-full.key"
serveur_loki_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc" serveur_loki_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc"
serveur_loki_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" serveur_loki_depot_source: "deb [signed-by=/etc/apt/keyrings/grafana.asc] {{ serveur_loki_depot_schema }}://apt.grafana.com stable main"
# --- TLS (zero-confiance) : Loki sert son API HTTP en HTTPS (cert step_ca) --- # --- TLS (zero-confiance) : Loki sert son API HTTP en HTTPS (cert step_ca) ---
serveur_loki_tls_actif: false serveur_loki_tls_actif: false
@ -33,3 +33,19 @@ serveur_loki_tls_source_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_
serveur_loki_sonde_schema: "{{ 'https' if serveur_loki_tls_actif | bool else 'http' }}" serveur_loki_sonde_schema: "{{ 'https' if serveur_loki_tls_actif | bool else 'http' }}"
serveur_loki_sonde_url: >- serveur_loki_sonde_url: >-
{{ serveur_loki_sonde_schema }}://127.0.0.1:{{ serveur_loki_port }} {{ serveur_loki_sonde_schema }}://127.0.0.1:{{ serveur_loki_port }}
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
serveur_loki_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -30,6 +30,24 @@
group: root group: root
mode: "0644" mode: "0644"
# `apt_repository` AJOUTE, IL NE REMPLACE PAS (mesure du 2026-09-10).
#
# Passer une source de `https://` a `http://` y ecrit une SECONDE ligne au lieu de
# corriger la premiere. apt interroge alors les deux — et l'ancienne continue de sortir
# en direct sur Internet, ce que le passage au cache visait justement a supprimer. Le
# symptome le disait, sur les quatorze machines :
#
# W: La cible Packages est specifiee plusieurs fois dans grafana.list:1 et :2
#
# On retire donc explicitement la forme precedente. `state: absent` ne mord que si la
# ligne existe : sur une machine neuve, cette tache ne fait rien.
- name: Retirer la forme HTTPS du depot (elle contournait le cache)
ansible.builtin.apt_repository:
repo: "{{ serveur_loki_depot_source | replace('http://', 'https://') }}"
filename: grafana
state: absent
when: serveur_loki_depot_schema == 'http'
- name: Ajouter le depot apt Grafana - name: Ajouter le depot apt Grafana
ansible.builtin.apt_repository: ansible.builtin.apt_repository:
repo: "{{ serveur_loki_depot_source }}" repo: "{{ serveur_loki_depot_source }}"

View file

@ -5,9 +5,9 @@ serveur_step_ca_paquets:
- step-ca - step-ca
# Depot apt officiel Smallstep. # Depot apt officiel Smallstep.
serveur_step_ca_depot_cle_url: "https://packages.smallstep.com/keys/apt/repo-signing-key.gpg" serveur_step_ca_depot_cle_url: "{{ serveur_step_ca_depot_schema }}://packages.smallstep.com/keys/apt/repo-signing-key.gpg"
serveur_step_ca_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc" serveur_step_ca_depot_cle_fichier: "/etc/apt/keyrings/smallstep.asc"
serveur_step_ca_depot_uri: "https://packages.smallstep.com/stable/debian" serveur_step_ca_depot_uri: "{{ serveur_step_ca_depot_schema }}://packages.smallstep.com/stable/debian"
serveur_step_ca_depot_suite: "debs" serveur_step_ca_depot_suite: "debs"
# Service. # Service.
@ -32,3 +32,19 @@ serveur_step_ca_sonde_racine: "/etc/step/certs/root_ca.crt"
# Une racine se remplace par une operation lourde et coordonnee : trois mois d'avance # Une racine se remplace par une operation lourde et coordonnee : trois mois d'avance
# n'est pas de la prudence, c'est le minimum pour l'organiser. # n'est pas de la prudence, c'est le minimum pour l'organiser.
serveur_step_ca_sonde_racine_jours: 90 serveur_step_ca_sonde_racine_jours: 90
# --- SCHEMA DES DEPOTS TIERS (2026-09-10) --------------------------------------------
#
# `http` DES QU'UN CACHE EST DANS LE CHEMIN, `https` sinon. Ce n'est pas un relachement :
# le cache RELAIE ces depots en https vers le fournisseur (voir `serveur_artefacts`,
# `Remap-*`), et l'integrite vient des SIGNATURES du depot, qu'apt verifie de toute facon.
# Le meme raisonnement vaut deja pour `deb.debian.org` depuis toujours.
#
# Sans cette derivation, chaque machine sortait elle-meme sur Internet : le mandataire ne
# vaut que pour `http`, et le socle pose deliberement `Acquire::https::Proxy "DIRECT"`
# parce qu'un cache sans remap refuse les tunnels. Le remap leve ce refus ; encore
# faut-il DEMANDER en http.
#
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
serveur_step_ca_depot_schema: >-
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}

View file

@ -3037,6 +3037,47 @@ def preuve_documentation_outillage() -> tuple[bool, str]:
# #
# executeur = liste de commandes argv (toutes doivent renvoyer 0), ou callable -> (ok, detail). # executeur = liste de commandes argv (toutes doivent renvoyer 0), ou callable -> (ok, detail).
def preuve_depots_tiers_par_le_cache() -> tuple[bool, str]:
"""Aucun role ne vise un depot tiers en `https://` ecrit en dur.
LE CACHE NE SERT QUE CE QU'ON LUI DEMANDE EN CLAIR (2026-09-10). Les trois
fournisseurs applicatifs de Set-OPS — Grafana, Icinga, Smallstep — ne publient qu'en
HTTPS, et `apt-cacher-ng` ne relaie pas un tunnel. Tant qu'un role ecrit `https://`,
la machine sort elle-meme sur Internet, quel que soit le mandataire configure.
Le remede est en deux moities, et une seule ne sert a rien : le cache DECLARE un
`Remap-*` par fournisseur, et chaque role DEMANDE en `{{ ..._depot_schema }}://`, qui
vaut `http` des qu'un cache d'amorcage est declare. Cette preuve garde la seconde
moitie — la premiere se voit dans `serveur_artefacts_remaps`.
Elle vaut surtout pour les roles A VENIR : le jour ou l'on ajoutera un depot tiers,
la copie du geste sera refusee ici plutot que decouverte a la prochaine mesure de
trafic sortant.
"""
hotes = [r["hote"] for r in (
yaml.safe_load((RACINE / "roles" / "serveur_artefacts" / "defaults" / "main.yml")
.read_text(encoding="utf-8")) or {}
).get("serveur_artefacts_remaps") or []]
if not hotes:
return False, "`serveur_artefacts_remaps` est vide : aucun depot tiers n'est relaye."
fautifs: list[str] = []
for chemin in sorted((RACINE / "roles").glob("*/defaults/main.yml")):
texte = chemin.read_text(encoding="utf-8")
for ligne_no, ligne in enumerate(texte.splitlines(), 1):
nu = ligne.strip()
if nu.startswith("#"):
continue
for h in hotes:
if f"https://{h}" in nu:
fautifs.append(f"{chemin.relative_to(RACINE)}:{ligne_no} vise "
f"https://{h} en dur")
if fautifs:
return False, ("Depot tiers vise en HTTPS direct — la machine sortira sur Internet "
"au lieu de passer par le cache :\n - " + "\n - ".join(fautifs))
return True, (f"{len(hotes)} depot(s) tiers relaye(s) par le cache, aucun role ne les "
f"vise en https:// ecrit en dur.")
PREUVES: list[dict] = [ PREUVES: list[dict] = [
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"], {"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
"cmds": [["ansible-lint", "-q"]]}, "cmds": [["ansible-lint", "-q"]]},
@ -3185,6 +3226,8 @@ PREUVES: list[dict] = [
"func": preuve_devis_frontiere_du_site}, "func": preuve_devis_frontiere_du_site},
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [], {"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]}, "cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
{"id": "P65", "titre": "Depots tiers : demandes au cache, jamais en HTTPS direct",
"refs": [], "func": preuve_depots_tiers_par_le_cache},
] ]