rotation : les comptes de secours peuvent enfin changer de mot de passe

Le runbook promettait cette rotation ; le code ne savait pas la faire. Les trois
roles ne posaient le mot de passe qu'a la CREATION — regenerer la voute aurait
produit le mensonge silencieux corrige toute la journee.

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

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-07 21:10:47 -04:00
parent df8ffb68d6
commit 91d3a736e7
7 changed files with 187 additions and 0 deletions

View file

@ -2,6 +2,45 @@
## 2026-08-06 — le chemin nord-sud devient dérivable ## 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 à `<homepath>/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 ### Icinga Web 2 cesse de nommer des personnes
Le dernier `porte_par: liste-uid` du catalogue. `serveur_icingaweb2_admins` valait un `uid` Le dernier `porte_par: liste-uid` du catalogue. `serveur_icingaweb2_admins` valait un `uid`

View file

@ -82,3 +82,6 @@ serveur_forgejo_db_sslrootcert: "/etc/step/certs/root_ca.crt"
# LDAP (group-ldap-mapper). Voir docs/autorisation.md. # LDAP (group-ldap-mapper). Voir docs/autorisation.md.
serveur_forgejo_oidc_groupe_claim: "groups" serveur_forgejo_oidc_groupe_claim: "groups"
serveur_forgejo_oidc_groupe_admin: "sysadmin" 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"

View file

@ -196,3 +196,47 @@
when: when:
- serveur_forgejo_oidc_actif | bool - serveur_forgejo_oidc_actif | bool
- not ansible_check_mode - 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

View file

@ -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_url: "https://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] 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"

View file

@ -102,3 +102,53 @@
enabled: true enabled: true
state: started state: started
daemon_reload: true 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
# `<homepath>/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

View file

@ -112,3 +112,6 @@ serveur_nextcloud_bruteforce_whitelist: "{{ (sous_reseau_admin | default('')) }}
# verse les membres du groupe d'habilitation. # verse les membres du groupe d'habilitation.
serveur_nextcloud_oidc_groupe_claim: "groups" serveur_nextcloud_oidc_groupe_claim: "groups"
serveur_nextcloud_oidc_groupe_admin: "sysadmin" 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"

View file

@ -77,3 +77,43 @@
when: when:
- serveur_nextcloud_bruteforce_whitelist | length > 0 - serveur_nextcloud_bruteforce_whitelist | length > 0
- "'/' in serveur_nextcloud_bruteforce_whitelist" - "'/' 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