From c6a7150c603b6092dd059ced02d889e25085be37 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 10 Sep 2026 14:53:48 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q --- AGENTS.md | 2 +- CHANGELOG.md | 95 +++++++++++++++++++ docs/audit/preuve-2026-09-10.md | 15 +-- docs/devis-services.md | 2 +- playbooks/groupes/serveur_debian.yml | 18 ++++ roles/client_journal/defaults/main.yml | 20 +++- roles/client_journal/tasks/main.yml | 18 ++++ roles/client_pki/defaults/main.yml | 20 +++- roles/client_sante/tasks/main.yml | 69 ++++++++++++++ roles/serveur_artefacts/defaults/main.yml | 38 ++++++++ .../templates/zzz-setops.conf.j2 | 27 ++++++ roles/serveur_collabora/defaults/main.yml | 10 +- roles/serveur_grafana/defaults/main.yml | 20 +++- roles/serveur_grafana/tasks/main.yml | 18 ++++ roles/serveur_icinga/defaults/main.yml | 20 +++- roles/serveur_icinga/tasks/main.yml | 18 ++++ roles/serveur_loki/defaults/main.yml | 20 +++- roles/serveur_loki/tasks/main.yml | 18 ++++ roles/serveur_step_ca/defaults/main.yml | 20 +++- scripts/prouver.py | 43 +++++++++ 20 files changed, 488 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bf1ee68..198d276 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente ## Écrire, puis relire (D-68) `--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 dépôt annonce.** diff --git a/CHANGELOG.md b/CHANGELOG.md index 16457e0..3c7d9d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,100 @@ # 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 Seconde reconstruction complete de Chezlepro dans la journee, cette fois pour EPROUVER les diff --git a/docs/audit/preuve-2026-09-10.md b/docs/audit/preuve-2026-09-10.md index eb14714..8390446 100644 --- a/docs/audit/preuve-2026-09-10.md +++ b/docs/audit/preuve-2026-09-10.md @@ -7,7 +7,7 @@ > [`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` -- **Verdict** : ✅ CONFORME (64 OK · 0 echec · 0 saute) +- **Verdict** : ✅ CONFORME (65 OK · 0 echec · 0 saute) ## 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. | | P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. | | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | -| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 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. | | 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. | @@ -37,15 +37,15 @@ | 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. | | 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). | | 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. | | 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. | | 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). | -| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). | +| 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 (36 groupes). | | 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). | | 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). | | 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). | -| 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 | | 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 | | 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. | | 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. | | 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). | @@ -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). | | 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_ | +| 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 diff --git a/docs/devis-services.md b/docs/devis-services.md index 8e14f06..af2b61d 100644 --- a/docs/devis-services.md +++ b/docs/devis-services.md @@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met ## 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 cohérent **avec lui-même** — que les handlers existent, que les intrants ont un propriétaire, que rien n'est codé en dur. diff --git a/playbooks/groupes/serveur_debian.yml b/playbooks/groupes/serveur_debian.yml index 889bcf0..347adf5 100644 --- a/playbooks/groupes/serveur_debian.yml +++ b/playbooks/groupes/serveur_debian.yml @@ -79,6 +79,24 @@ - artefacts_amorcage is defined - 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 ansible.builtin.slurp: src: /etc/resolv.conf diff --git a/roles/client_journal/defaults/main.yml b/roles/client_journal/defaults/main.yml index 80a3e8c..6dc5283 100644 --- a/roles/client_journal/defaults/main.yml +++ b/roles/client_journal/defaults/main.yml @@ -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" # 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_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 --- client_journal_loki_tls: false @@ -41,3 +41,19 @@ client_journal_sonde_url: >- 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. 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' }} diff --git a/roles/client_journal/tasks/main.yml b/roles/client_journal/tasks/main.yml index ce21093..7a38394 100644 --- a/roles/client_journal/tasks/main.yml +++ b/roles/client_journal/tasks/main.yml @@ -30,6 +30,24 @@ group: root 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 ansible.builtin.apt_repository: repo: "{{ client_journal_depot_source }}" diff --git a/roles/client_pki/defaults/main.yml b/roles/client_pki/defaults/main.yml index 8521d61..963591f 100644 --- a/roles/client_pki/defaults/main.yml +++ b/roles/client_pki/defaults/main.yml @@ -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) # 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_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" # 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 # (l'hote n'expose rien en TLS, ou son consommateur n'ecoute pas en local). 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' }} diff --git a/roles/client_sante/tasks/main.yml b/roles/client_sante/tasks/main.yml index 762e9f8..3116e62 100644 --- a/roles/client_sante/tasks/main.yml +++ b/roles/client_sante/tasks/main.yml @@ -72,6 +72,75 @@ name: curl 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é ansible.builtin.template: src: setops-sante.sh.j2 diff --git a/roles/serveur_artefacts/defaults/main.yml b/roles/serveur_artefacts/defaults/main.yml index 1ea2749..5888a6b 100644 --- a/roles/serveur_artefacts/defaults/main.yml +++ b/roles/serveur_artefacts/defaults/main.yml @@ -73,3 +73,41 @@ serveur_artefacts_sonde_repertoire: /var/cache/apt-cacher-ng # ecrire. On prend de l'avance. serveur_artefacts_sonde_pct_avert: 85 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://` -> `https:///`. +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)$' diff --git a/roles/serveur_artefacts/templates/zzz-setops.conf.j2 b/roles/serveur_artefacts/templates/zzz-setops.conf.j2 index 8f81f25..c207e56 100644 --- a/roles/serveur_artefacts/templates/zzz-setops.conf.j2 +++ b/roles/serveur_artefacts/templates/zzz-setops.conf.j2 @@ -23,3 +23,30 @@ Offlinemode: 0 # Debian n'est ainsi téléchargé qu'une fois pour tout le site. Proxy: {{ serveur_artefacts_amont }} {% 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 %} diff --git a/roles/serveur_collabora/defaults/main.yml b/roles/serveur_collabora/defaults/main.yml index fc7b112..fbd48be 100644 --- a/roles/serveur_collabora/defaults/main.yml +++ b/roles/serveur_collabora/defaults/main.yml @@ -25,8 +25,8 @@ serveur_collabora_port: 9980 # # Dépôt PLAT : `Suites: ./` et pas de composant. Une source classique (`deb … stable # main`) ne le trouverait pas. -serveur_collabora_depot_uri: "https://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_uri: "{{ serveur_collabora_depot_schema }}://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb" +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" # --- 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_utilisateur: "collabora-admin" 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' }} diff --git a/roles/serveur_grafana/defaults/main.yml b/roles/serveur_grafana/defaults/main.yml index 7a883f2..39ead6c 100644 --- a/roles/serveur_grafana/defaults/main.yml +++ b/roles/serveur_grafana/defaults/main.yml @@ -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'" # 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_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 # en place (impossible : il est hache). Voir tasks/main.yml. @@ -61,3 +61,19 @@ serveur_grafana_donnees: "/var/lib/grafana" # --- Sonde de supervision ----------------------------------------------------------- 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' }} diff --git a/roles/serveur_grafana/tasks/main.yml b/roles/serveur_grafana/tasks/main.yml index e3fcdcb..8ba51df 100644 --- a/roles/serveur_grafana/tasks/main.yml +++ b/roles/serveur_grafana/tasks/main.yml @@ -62,6 +62,24 @@ group: root 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 ansible.builtin.apt_repository: repo: "{{ serveur_grafana_depot_source }}" diff --git a/roles/serveur_icinga/defaults/main.yml b/roles/serveur_icinga/defaults/main.yml index 5b75ac8..647ed25 100644 --- a/roles/serveur_icinga/defaults/main.yml +++ b/roles/serveur_icinga/defaults/main.yml @@ -34,10 +34,10 @@ serveur_icinga_service_redis: "icingadb-redis" # Depot apt officiel Icinga (paquet trousseau + source signee). 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: >- 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. serveur_icinga_groupe: "serveur_icinga" @@ -138,3 +138,19 @@ serveur_icinga_sonde_config_db: /etc/icingadb/config.yml # minute de silence est deja anormale. serveur_icinga_sonde_age_avert: 60 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' }} diff --git a/roles/serveur_icinga/tasks/main.yml b/roles/serveur_icinga/tasks/main.yml index 9eceec3..6376ab9 100644 --- a/roles/serveur_icinga/tasks/main.yml +++ b/roles/serveur_icinga/tasks/main.yml @@ -25,6 +25,24 @@ deb: "/tmp/icinga-archive-keyring.deb" 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 ansible.builtin.apt_repository: repo: "{{ serveur_icinga_depot_source }}" diff --git a/roles/serveur_loki/defaults/main.yml b/roles/serveur_loki/defaults/main.yml index 38cdb31..55b111b 100644 --- a/roles/serveur_loki/defaults/main.yml +++ b/roles/serveur_loki/defaults/main.yml @@ -9,9 +9,9 @@ serveur_loki_chemin: "/var/lib/loki" serveur_loki_retention: "744h" # 31 jours # 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_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) --- 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_url: >- {{ 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' }} diff --git a/roles/serveur_loki/tasks/main.yml b/roles/serveur_loki/tasks/main.yml index 597c77a..2506473 100644 --- a/roles/serveur_loki/tasks/main.yml +++ b/roles/serveur_loki/tasks/main.yml @@ -30,6 +30,24 @@ group: root 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 ansible.builtin.apt_repository: repo: "{{ serveur_loki_depot_source }}" diff --git a/roles/serveur_step_ca/defaults/main.yml b/roles/serveur_step_ca/defaults/main.yml index a3eade4..c152f99 100644 --- a/roles/serveur_step_ca/defaults/main.yml +++ b/roles/serveur_step_ca/defaults/main.yml @@ -5,9 +5,9 @@ serveur_step_ca_paquets: - step-ca # 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_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" # 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 # n'est pas de la prudence, c'est le minimum pour l'organiser. 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' }} diff --git a/scripts/prouver.py b/scripts/prouver.py index 8cdd0cd..b6a4716 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -3037,6 +3037,47 @@ def preuve_documentation_outillage() -> tuple[bool, str]: # # 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] = [ {"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"], "cmds": [["ansible-lint", "-q"]]}, @@ -3185,6 +3226,8 @@ PREUVES: list[dict] = [ "func": preuve_devis_frontiere_du_site}, {"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [], "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}, ]