diff --git a/CHANGELOG.md b/CHANGELOG.md index c7017c7..0883a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,45 @@ ## 2026-08-06 — le chemin nord-sud devient dérivable +### La rotation des comptes de secours devient possible — elle ne l'était pas + +Le runbook §6.6 promettait de régénérer les comptes de secours ; **le code ne savait pas le +faire**. Les trois rôles ne posaient le mot de passe qu'à la *création* : + +``` +grafana GF_SECURITY_ADMIN_PASSWORD n'agit qu'à la création du compte +forgejo admin user create `creates: .admin-created` — une seule fois +nextcloud maintenance:install seulement à l'installation +``` + +Régénérer la voûte sans cela aurait produit exactement le mensonge silencieux corrigé toute +la journée : la voûte dit une chose, le service en a une autre. + +Chaque rôle sait désormais **changer** un mot de passe existant, avec une idempotence par +**empreinte du secret appliqué** — on ne peut pas relire un hachage, donc on mémorise ce qu'on +a posé. Pour Nextcloud, le secret passe par l'environnement (`--password-from-env`) et non par +la ligne de commande, où il serait visible dans la table des processus. + +### `grafana-cli` écrivait dans une base fantôme + +Et disait « Admin password changed successfully ✔ » à chaque fois. + +La CLI prend `paths.data` par défaut à `/data` ; le paquet Debian range la base dans +`/var/lib/grafana`. Elle créait donc `/usr/share/grafana/data/grafana.db`, y écrivait, et +annonçait le succès — pendant que le serveur lisait l'autre fichier. + +**Ce qui l'a démasqué** : le champ `updated` du compte, resté à l'heure du déploiement initial +malgré quatre réinitialisations « réussies ». Un message de succès n'est pas une preuve ; +l'état l'est. `--configOverrides=cfg:default.paths.data=…` est désormais imposé. + +**Vérifié par authentification réelle** : Grafana `200`, Nextcloud `200`. Pour Forgejo, +`ENABLE_BASIC_AUTHENTICATION = false` ferme l'API par doctrine (D-41) — la seule preuve +disponible est le retour de la commande, qui confirme le changement. + +Quatre secrets régénérés : `vault_grafana_admin`, `vault_forgejo_admin`, +`vault_nextcloud_admin`, `vault_sysadmin_amorcage`. + + ### Icinga Web 2 cesse de nommer des personnes Le dernier `porte_par: liste-uid` du catalogue. `serveur_icingaweb2_admins` valait un `uid` diff --git a/roles/serveur_forgejo/defaults/main.yml b/roles/serveur_forgejo/defaults/main.yml index 15a23af..87ad0fe 100644 --- a/roles/serveur_forgejo/defaults/main.yml +++ b/roles/serveur_forgejo/defaults/main.yml @@ -82,3 +82,6 @@ serveur_forgejo_db_sslrootcert: "/etc/step/certs/root_ca.crt" # LDAP (group-ldap-mapper). Voir docs/autorisation.md. serveur_forgejo_oidc_groupe_claim: "groups" serveur_forgejo_oidc_groupe_admin: "sysadmin" + +# Empreinte du mot de passe admin APPLIQUE (rotation). Voir tasks/main.yml. +serveur_forgejo_marqueur_admin: "/etc/forgejo/.setops-admin-sha256" diff --git a/roles/serveur_forgejo/tasks/main.yml b/roles/serveur_forgejo/tasks/main.yml index 4c9be3b..5b51227 100644 --- a/roles/serveur_forgejo/tasks/main.yml +++ b/roles/serveur_forgejo/tasks/main.yml @@ -196,3 +196,47 @@ when: - serveur_forgejo_oidc_actif | bool - not ansible_check_mode + +# --- ROTATION DU COMPTE DE SECOURS ------------------------------------------- +# `admin user create` porte `creates: .admin-created` : il ne tourne qu'UNE fois. +# Changer la voute ne changeait donc rien, et la voute se mettait a mentir. Meme +# raisonnement que pour Grafana — idempotence par EMPREINTE du secret applique. +- name: Empreinte du mot de passe admin en place + ansible.builtin.slurp: + src: "{{ serveur_forgejo_marqueur_admin }}" + register: serveur_forgejo_marque + failed_when: false + changed_when: false + no_log: true + +- name: Appliquer le mot de passe du compte de secours (rotation) + ansible.builtin.command: + argv: + - "{{ serveur_forgejo_binaire }}" + - "--config" + - "{{ serveur_forgejo_config }}" + - admin + - user + - change-password + - "--username" + - "{{ serveur_forgejo_admin_user }}" + - "--password" + - "{{ serveur_forgejo_admin_password }}" + environment: + GITEA_WORK_DIR: "{{ serveur_forgejo_data }}" + become: true + become_user: "{{ serveur_forgejo_utilisateur }}" + changed_when: true + no_log: true + when: >- + (serveur_forgejo_marque.content | default('') | b64decode | trim) + != (serveur_forgejo_admin_password | hash('sha256')) + +- name: Retenir l'empreinte appliquee + ansible.builtin.copy: + content: "{{ serveur_forgejo_admin_password | hash('sha256') }}\n" + dest: "{{ serveur_forgejo_marqueur_admin }}" + owner: root + group: root + mode: "0600" + no_log: true diff --git a/roles/serveur_grafana/defaults/main.yml b/roles/serveur_grafana/defaults/main.yml index 4695fd7..c28c089 100644 --- a/roles/serveur_grafana/defaults/main.yml +++ b/roles/serveur_grafana/defaults/main.yml @@ -50,3 +50,11 @@ serveur_grafana_oidc_role_path: "contains(roles[*], 'grafana-admin') && 'Admin' serveur_grafana_depot_cle_url: "https://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" + +# Empreinte du mot de passe admin APPLIQUE — permet la rotation sans lire le secret +# en place (impossible : il est hache). Voir tasks/main.yml. +serveur_grafana_marqueur_admin: "/etc/grafana/.setops-admin-sha256" + +# Chemins du paquet Debian — la CLI ne les devine pas (voir tasks/main.yml). +serveur_grafana_homepath: "/usr/share/grafana" +serveur_grafana_donnees: "/var/lib/grafana" diff --git a/roles/serveur_grafana/tasks/main.yml b/roles/serveur_grafana/tasks/main.yml index eabe9ad..2d34853 100644 --- a/roles/serveur_grafana/tasks/main.yml +++ b/roles/serveur_grafana/tasks/main.yml @@ -102,3 +102,53 @@ enabled: true state: started daemon_reload: true + +# --- ROTATION DU COMPTE DE SECOURS ------------------------------------------- +# `GF_SECURITY_ADMIN_PASSWORD` n'agit qu'a la CREATION du compte : changer la voute +# ne changeait rien, et la voute se mettait a mentir en silence. Le runbook de +# reprise (docs/autorisation.md §6) promet pourtant cette rotation — c'est ce qui +# transforme une livraison en transfert. Constate le 2026-08-07. +# +# Idempotence par EMPREINTE : on garde le sha256 du secret applique. Tant qu'il ne +# change pas, on ne touche a rien ; des qu'il change, on reapplique. On ne peut pas +# lire le mot de passe en place, donc on memorise ce qu'on a pose. +- name: Empreinte du mot de passe admin en place + ansible.builtin.slurp: + src: "{{ serveur_grafana_marqueur_admin }}" + register: serveur_grafana_marque + failed_when: false + changed_when: false + no_log: true + +- name: Appliquer le mot de passe du compte de secours (rotation) + ansible.builtin.command: + argv: + - grafana-cli + - "--homepath={{ serveur_grafana_homepath }}" + # `paths.data` DOIT etre impose : la CLI le prend par defaut a + # `/data`, alors que le paquet Debian range la base dans + # /var/lib/grafana. Sans lui, la CLI cree une base FANTOME, y ecrit, et + # annonce « Admin password changed successfully » — pendant que le serveur + # lit l'autre. Constate le 2026-08-07 : le compte n'avait pas bouge depuis + # le deploiement initial, malgre quatre reinitialisations « reussies ». + - "--configOverrides=cfg:default.paths.data={{ serveur_grafana_donnees }}" + - admin + - reset-admin-password + - "{{ serveur_grafana_admin_password }}" + no_log: true + # La tache ne s'execute QUE si l'empreinte differe : quand elle tourne, elle + # change reellement le mot de passe. + changed_when: true + when: >- + (serveur_grafana_marque.content | default('') | b64decode | trim) + != (serveur_grafana_admin_password | hash('sha256')) + notify: Redemarrer grafana + +- name: Retenir l'empreinte appliquee + ansible.builtin.copy: + content: "{{ serveur_grafana_admin_password | hash('sha256') }}\n" + dest: "{{ serveur_grafana_marqueur_admin }}" + owner: root + group: root + mode: "0600" + no_log: true diff --git a/roles/serveur_nextcloud/defaults/main.yml b/roles/serveur_nextcloud/defaults/main.yml index 9d9f6e9..0ee18a8 100644 --- a/roles/serveur_nextcloud/defaults/main.yml +++ b/roles/serveur_nextcloud/defaults/main.yml @@ -112,3 +112,6 @@ serveur_nextcloud_bruteforce_whitelist: "{{ (sous_reseau_admin | default('')) }} # verse les membres du groupe d'habilitation. serveur_nextcloud_oidc_groupe_claim: "groups" serveur_nextcloud_oidc_groupe_admin: "sysadmin" + +# Empreinte du mot de passe admin APPLIQUE (rotation). Voir tasks/configurer.yml. +serveur_nextcloud_marqueur_admin: "/etc/setops-nextcloud-admin-sha256" diff --git a/roles/serveur_nextcloud/tasks/configurer.yml b/roles/serveur_nextcloud/tasks/configurer.yml index 951bfec..aa346a5 100644 --- a/roles/serveur_nextcloud/tasks/configurer.yml +++ b/roles/serveur_nextcloud/tasks/configurer.yml @@ -77,3 +77,43 @@ when: - serveur_nextcloud_bruteforce_whitelist | length > 0 - "'/' in serveur_nextcloud_bruteforce_whitelist" + +# --- ROTATION DU COMPTE DE SECOURS ------------------------------------------- +# `maintenance:install --admin-pass` ne tourne qu'a l'INSTALLATION. Changer la +# voute ne changeait rien. `user:resetpassword` lit le secret dans l'environnement +# (`--password-from-env`) : il ne passe donc pas par la ligne de commande, ou il +# serait visible dans la table des processus. +- name: Empreinte du mot de passe admin en place + ansible.builtin.slurp: + src: "{{ serveur_nextcloud_marqueur_admin }}" + register: serveur_nextcloud_marque + failed_when: false + changed_when: false + no_log: true + +- name: Appliquer le mot de passe du compte de secours (rotation) + ansible.builtin.command: + argv: + - php + - "{{ serveur_nextcloud_racine }}/occ" + - "user:resetpassword" + - "--password-from-env" + - "{{ serveur_nextcloud_admin_user }}" + environment: + OC_PASS: "{{ serveur_nextcloud_admin_password }}" + become: true + become_user: "{{ serveur_nextcloud_utilisateur | default('www-data') }}" + changed_when: true + no_log: true + when: >- + (serveur_nextcloud_marque.content | default('') | b64decode | trim) + != (serveur_nextcloud_admin_password | hash('sha256')) + +- name: Retenir l'empreinte appliquee + ansible.builtin.copy: + content: "{{ serveur_nextcloud_admin_password | hash('sha256') }}\n" + dest: "{{ serveur_nextcloud_marqueur_admin }}" + owner: root + group: root + mode: "0600" + no_log: true