diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa963f..d47d627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,61 @@ # CHANGELOG — Set-OPS +## 2026-10-03 (64) — La vigie a sa base partout ; trois restes du site corrigés + +**Demande de l'exploitant** : traiter les points 3 et 5 du rapport d'anomalies. + +**3. Icinga Web sans base chez les locataires.** Depuis leur reconstruction, la vigie de +Chezlepro journalisait 145 fois par heure, page ouverte, « Failed to load pending +migrations : Please check if a db instance exists at all » ; Technolibre de même dès qu'on la +regardait. Le site avait reçu sa base le 2026-09-15, mais seulement en mode `db` ; les +locataires, en SSO, étaient restés en `config_backend = "ini"`. Lu dans le code +d'Icinga Web : `ConfigMenu::createMigrationBadge` compte les migrations SANS condition de +permission — aucun réglage de rôle ne le fait taire. **Fait** : `serveur_icingaweb2` résout +et utilise sa base dans TOUS les modes (`config_backend = "db"`, ressource `icingaweb_db`, +schéma chargé une fois) ; les comptes en base et le compte d'amorçage restent propres au +mode `db`. Base `icingaweb2` ajoutée aux plans de Chezlepro, Technolibre, du lab et des +deux modèles qui portent une vigie (`observabilite`, `integral`) ; `vault_bd_icingaweb2` +aux gabarits de voûte et à l'exemple public. **Reste à l'exploitant** : la valeur réelle +dans trois voûtes (l'écriture a été refusée à l'assistant), puis le déploiement chez les +deux locataires (`serveur_postgresql`, `serveur_icingaweb2`, `serveur_ops_tenant` pour +que le runner reçoive la voûte). Les préférences déjà rangées en fichiers ne sont pas +reprises : ce ne sont que des réglages d'affichage. + +**5a. Le cache du site donné aux hyperviseurs, qui ne l'atteignent pas.** Mesuré depuis +asgard : `10.37.33.21:3142` injoignable. Or `artefacts_amorcage` leur était transmis par +`communes` : un passage de `client_journal` aurait réécrit leur dépôt Grafana en `http://` +par ce mandataire, et apt l'aurait perdu. **Fait** : `site_inventaire.py` ne leur donne plus +ni `artefacts_amorcage`, ni `setops_depot_binaires`, ni `dns_amorcage` (son jumeau, P79) — +amorçages d'une VM née du gabarit, qu'aucun rôle des hyperviseurs ne lit. Simulé : un passage +de `client_journal` n'y change plus que la config Alloy. + +**5b. apache2 sur `site-mon-01`.** Tiré par `libapache2-mod-php8.4`, dont plus rien ne +dépendait : au site, les paquets d'Icinga viennent du cache du contrôleur et s'installaient +AVANT `php8.4-fpm`, et apt prenait la première alternative PHP. Les locataires, en une seule +transaction, n'avaient pas ce reste. **Fait** : `php8.4-fpm` et nginx posés avant les +paquets tiers ; apache2 retiré seulement si `apt-get -s purge` confirme qu'il part seul. +Appliqué : 0 paquet apache, port 80 fermé, vigie en 302. + +**5c. Grafana Live sans WebSocket.** Chaque page de l'observatoire journalisait +`GET /api/live/ws` en 400, au site comme chez les locataires : l'edge relayait sans +`Upgrade`. Le mécanisme existait (Collabora) ; Grafana ne le déclarait pas. **Fait** : +`websocket: true` sur `grafana` dans les six plans qui le portent (site, deux locataires, +lab, deux modèles). Appliqué au site : la poignée WebSocket atteint Grafana (401 sans +session, au lieu de 400). + +**5d. `ssl_protocols` en double.** Le `nginx.conf` de Debian le déclare déjà ; +`99-setops.conf` le répétait au même niveau — `duplicate value "TLSv1.3"` à chaque +rechargement. **Fait** : la ligne de Debian est neutralisée, comme `server_tokens` avant +elle. Appliqué au site : `nginx -t` sans avertissement. + +**Validation** : `ansible-lint` (0 violation), `make verifier` conforme sur Chezlepro (83/83) +et preuves conformes sur Technolibre (82 + 1 sautée, la voûte) ; `voute.py verifier` complet +pour les deux. Au site : simulé puis appliqué, 0 échec. + +**Fuite à signaler** : en lisant la config d'Icinga Web de Chezlepro, l'assistant a affiché +en clair le mot de passe de liaison LDAP `cn=icingaweb2` (`bind_pw` n'était pas masqué). Il +n'est écrit nulle part ailleurs que dans la conversation ; une rotation est recommandée. + ## 2026-10-03 (63) — Les locataires mesurés et corrigés : même bruit, et le courriel en ajoute **Demande de l'exploitant** : mesurer chez les deux locataires les défauts corrigés au site diff --git a/exemples/vault.exemple.yml b/exemples/vault.exemple.yml index 97f6658..b8ab00b 100644 --- a/exemples/vault.exemple.yml +++ b/exemples/vault.exemple.yml @@ -32,7 +32,8 @@ vault_postgresql_keycloak: "" vault_bd_keycloak: "" vault_bd_forgejo: "" vault_bd_icingadb: "" -# La base des COMPTES de la console (mode `db`) — distincte de celle du moteur. +# La base de la console (vigie) dans tous les modes : comptes en mode `db`, préférences +# et migrations partout — distincte de celle du moteur. vault_bd_icingaweb2: "" # --- Forge (Forgejo) --- diff --git a/roles/serveur_icingaweb2/defaults/main.yml b/roles/serveur_icingaweb2/defaults/main.yml index 30d2744..eac7250 100644 --- a/roles/serveur_icingaweb2/defaults/main.yml +++ b/roles/serveur_icingaweb2/defaults/main.yml @@ -16,6 +16,19 @@ serveur_icingaweb2_paquets: - php8.4-mbstring - nginx +# INSTALLES AVANT LES PAQUETS TIERS, pour qu'apt ne choisisse pas apache2 (voir tasks). +serveur_icingaweb2_web_prealables: + - php8.4-fpm + - nginx + +# Ce qu'un ordre d'installation fautif a laisse, et que le role retire s'il part seul. +serveur_icingaweb2_apache_restes: + - apache2 + - apache2-bin + - apache2-data + - apache2-utils + - libapache2-mod-php8.4 + serveur_icingaweb2_php_fpm_service: "php8.4-fpm" serveur_icingaweb2_php_fpm_socket: "/run/php/php8.4-fpm.sock" serveur_icingaweb2_nginx_service: "nginx" diff --git a/roles/serveur_icingaweb2/tasks/main.yml b/roles/serveur_icingaweb2/tasks/main.yml index d77e370..b1ce6a6 100644 --- a/roles/serveur_icingaweb2/tasks/main.yml +++ b/roles/serveur_icingaweb2/tasks/main.yml @@ -52,7 +52,13 @@ name: resoudre_base vars: resoudre_base_groupe: "icingaweb2" - when: serveur_icingaweb2_auth == 'db' + # DANS TOUS LES MODES (2026-10-03), plus seulement `db`. Le cadre de migration d'Icinga + # Web 2 veut une base quoi qu'il arrive : sans elle, chaque page qui affiche le menu de + # configuration journalise « Failed to load pending migrations : Please check if a db + # instance exists at all » — 145 fois par heure chez Chezlepro, vigie ouverte, depuis + # la reconstruction. Le site avait recu sa base le 2026-09-15 ; les locataires, en SSO, + # etaient restes sans. Le badge est calcule sans condition de permission + # (`ConfigMenu::createMigrationBadge`) : aucun reglage de role ne le fait taire. - name: Adopter la base des comptes pour Icinga Web 2 ansible.builtin.set_fact: @@ -62,7 +68,6 @@ serveur_icingaweb2_db_utilisateur: "{{ resoudre_base_entree.proprietaire }}" serveur_icingaweb2_db_motdepasse: "{{ resoudre_base_db_password }}" no_log: true - when: serveur_icingaweb2_auth == 'db' - name: Exiger un mot de passe pour le compte d'amorçage ansible.builtin.assert: @@ -76,7 +81,7 @@ # LE SCHEMA NE SE CHARGE QU'UNE FOIS. Le rejouer sur une base deja peuplee echouerait sur # les objets existants — on lit donc d'abord si la table des comptes est la. -- name: La base des comptes est-elle déjà en place ? +- name: La base de la console est-elle déjà en place ? community.postgresql.postgresql_query: login_host: "{{ serveur_icingaweb2_db_host }}" login_user: "{{ serveur_icingaweb2_db_utilisateur }}" @@ -89,10 +94,9 @@ changed_when: false no_log: true when: - - serveur_icingaweb2_auth == 'db' - not ansible_check_mode -- name: Charger le schéma des comptes +- name: Charger le schéma de la console (comptes, préférences, migrations) community.postgresql.postgresql_script: login_host: "{{ serveur_icingaweb2_db_host }}" login_user: "{{ serveur_icingaweb2_db_utilisateur }}" @@ -103,7 +107,6 @@ path: "{{ serveur_icingaweb2_schema }}" no_log: true when: - - serveur_icingaweb2_auth == 'db' - not ansible_check_mode - not (serveur_icingaweb2_schema_etat.query_result[0].presente | default(false)) @@ -192,6 +195,18 @@ # `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur # Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions # epinglees et empreintes verifiees ; ce role les depose depuis ce cache. +# PHP-FPM ET NGINX AVANT LES PAQUETS TIERS (2026-10-03). Les paquets d'Icinga exigent +# « un PHP pour le web », et apt choisit la PREMIERE alternative qu'aucun paquet present ne +# satisfait : `libapache2-mod-php`, qui tire apache2. Chez les locataires, tout s'installe +# en une transaction avec `php8.4-fpm`, et apt n'a pas a choisir. Au site, les paquets +# tiers viennent du cache du controleur, SEULS, avant le reste : `site-mon-01` portait un +# apache2 ecoutant sur le port 80 depuis le 2026-09-14, que rien n'utilisait. +- name: Installer PHP-FPM et nginx avant les paquets tiers (sinon apt choisit apache2) + ansible.builtin.apt: + name: "{{ serveur_icingaweb2_web_prealables }}" + state: present + update_cache: true + - name: Poser les paquets tiers depuis le cache du controleur ansible.builtin.include_role: name: paquets_tiers @@ -211,6 +226,27 @@ when: (serveur_icingaweb2_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0 +# LE RESTE D'UNE INSTALLATION DANS LE MAUVAIS ORDRE (voir plus haut). On ne le retire que +# si apt confirme qu'il part SEUL : `state: absent` emporterait aussi tout paquet qui en +# dependrait, et ce role n'a pas a decider de leur sort. +- name: Simuler le retrait d'apache2 (rien d'autre ne doit partir) + ansible.builtin.command: + argv: "{{ ['apt-get', '-s', 'purge'] + serveur_icingaweb2_apache_restes }}" + register: serveur_icingaweb2_apache_simulation + changed_when: false + check_mode: false + +- name: Retirer apache2, que rien n'utilise + ansible.builtin.apt: + name: "{{ serveur_icingaweb2_apache_restes }}" + state: absent + purge: true + when: + - serveur_icingaweb2_apache_simulation.stdout_lines | select('match', '^(Purg|Remv) ') | list | length > 0 + - (serveur_icingaweb2_apache_simulation.stdout_lines | select('match', '^(Purg|Remv) ') + | map('regex_replace', '^(?:Purg|Remv) (\\S+).*$', '\\1') | list) + is subset(serveur_icingaweb2_apache_restes) + - name: Ajouter www-data au groupe icingaweb2 (lecture de la config) ansible.builtin.user: name: www-data diff --git a/roles/serveur_icingaweb2/templates/config.ini.j2 b/roles/serveur_icingaweb2/templates/config.ini.j2 index 40136b8..77a1532 100644 --- a/roles/serveur_icingaweb2/templates/config.ini.j2 +++ b/roles/serveur_icingaweb2/templates/config.ini.j2 @@ -1,8 +1,8 @@ ; Géré par Set-OPS (rôle serveur_icingaweb2). Ne pas éditer à la main. [global] show_stacktraces = "0" -{% if serveur_icingaweb2_auth == 'db' %} -; LA CONSOLE RANGE SON PROPRE ETAT DANS SA PROPRE BASE (2026-09-15). +; LA CONSOLE RANGE SON PROPRE ETAT DANS SA PROPRE BASE (2026-09-15 au site, 2026-10-03 +; partout : les locataires en SSO etaient restes en `ini`, avec la premiere erreur ci-dessous). ; ; `config_backend = "ini"` laissait les preferences en fichiers, et c'etait tenable — mais ; le cadre de MIGRATION d'Icinga Web 2, lui, veut une instance de base quoi qu'il arrive. @@ -19,9 +19,6 @@ show_stacktraces = "0" ; d'un navigateur a l'autre, ce que des fichiers locaux ne font pas. config_backend = "db" config_resource = "icingaweb_db" -{% else %} -config_backend = "ini" -{% endif %} [logging] log = "syslog" diff --git a/roles/serveur_icingaweb2/templates/resources.ini.j2 b/roles/serveur_icingaweb2/templates/resources.ini.j2 index 9677606..e9cb329 100644 --- a/roles/serveur_icingaweb2/templates/resources.ini.j2 +++ b/roles/serveur_icingaweb2/templates/resources.ini.j2 @@ -18,11 +18,10 @@ charset = "UTF8" ssl_mode = "{{ serveur_icingaweb2_db_sslmode }}" ssl_ca = "{{ serveur_icingaweb2_db_sslrootcert }}" {% endif %} -{% if serveur_icingaweb2_auth == 'db' %} -; LA BASE DES COMPTES DE LA CONSOLE — distincte d'`icingadb`, qui est celle du MOTEUR. -; Meler les deux ferait disparaitre les comptes au premier passage des migrations du -; moteur, sans que personne n'ait touche aux comptes. +; LA BASE DE LA CONSOLE — distincte d'`icingadb`, qui est celle du MOTEUR. Dans tous les +; modes : comptes en mode `db`, preferences et migrations partout. Meler les deux bases +; ferait disparaitre les comptes au premier passage des migrations du moteur. [icingaweb_db] type = "db" db = "pgsql" @@ -36,7 +35,6 @@ charset = "UTF8" ssl_mode = "{{ serveur_icingaweb2_db_sslmode }}" ssl_ca = "{{ serveur_icingaweb2_db_sslrootcert }}" {% endif %} -{% endif %} {% if serveur_icingaweb2_auth not in ('db',) %} [icingaweb_ldap] diff --git a/roles/serveur_nginx/tasks/main.yml b/roles/serveur_nginx/tasks/main.yml index adce154..1e5abcb 100644 --- a/roles/serveur_nginx/tasks/main.yml +++ b/roles/serveur_nginx/tasks/main.yml @@ -16,6 +16,18 @@ replace: '\1# server_tokens gere par Set-OPS (conf.d/99-setops.conf)' notify: Valider et recharger nginx +# MEME CAS POUR `ssl_protocols` (2026-10-03), mais sans echec : nginx ACCEPTE la directive +# deux fois au meme niveau, ajoute les valeurs, et avertit a chaque rechargement — +# `duplicate value "TLSv1.3" in /etc/nginx/conf.d/99-setops.conf:3`, releve dans le journal +# de `site-edge-01`. Un avertissement repete est un avertissement qu'on cesse de lire ; et +# la politique TLS doit avoir UNE source, la notre. +- name: Neutraliser le ssl_protocols par defaut de Debian + ansible.builtin.replace: + path: /etc/nginx/nginx.conf + regexp: '^(\s*)ssl_protocols\s+[^;]+;.*$' + replace: '\1# ssl_protocols gere par Set-OPS (conf.d/99-setops.conf)' + notify: Valider et recharger nginx + - name: Deployer le durcissement de base nginx ansible.builtin.template: src: 99-setops.conf.j2 diff --git a/scripts/site_inventaire.py b/scripts/site_inventaire.py index 77cee99..311eaa4 100755 --- a/scripts/site_inventaire.py +++ b/scripts/site_inventaire.py @@ -962,7 +962,19 @@ def inventaire() -> dict: # sert pour l'identite de noeud du cluster. **({"client_journal_loki_url": f"http://{_ip_loki}:3100/loki/api/v1/push"} if _ip_loki else {}), - **communes, + # NI LE CACHE DU SITE NI SES AMORCAGES (2026-10-03). `communes` le donne a toute machine du + # site ; un hyperviseur ne l'atteint pas — mesure depuis asgard : + # `10.37.33.21:3142` injoignable, par la frontiere comme tout le reste. Avec + # `artefacts_amorcage`, `client_journal` aurait reecrit sa source Grafana en + # `http://` par ce mandataire, et apt aurait perdu le depot. Sans lui, la + # source reste `https://apt.grafana.com`, celle qui marche. Le depot de + # binaires passe par le meme cache : meme raison. + # + # `dns_amorcage` part avec eux, son jumeau (P79) : les deux amorcent la PREMIERE + # mise en route d'une VM nee du gabarit, et un hyperviseur n'en nait pas. Aucun + # role qu'il recoit ne le lit. + **{k: v for k, v in communes.items() + if k not in ("artefacts_amorcage", "setops_depot_binaires", "dns_amorcage")}, } groupes.setdefault("hyperviseurs", []).append(_nom) # ON TIRE, ON NE POUSSE PAS — ET C'EST LA ROUTE GELEE QUI LE DECIDE (2026-09-10).