Set-OPS-Public/docs/runbooks-exploitation.md
Daniel Allaire f802e96b42 recette : la donnee REVIENT — restauration eprouvee, pas seulement sauvegarde
On savait que la donnee partait et arrivait. Pas qu'elle revenait.

Trois charges critiques eprouvees :
  - cles de l'AC (infra-pki-01) : restauration + comparaison OCTET POUR OCTET,
    12 fichiers, 11 identiques, root_ca_key et intermediate_ca_key compris. Seul
    ecart : db/000000.vlog, le journal badger de step-ca, qui avance a chaque
    emission. Attendu.
  - annuaire (idm-01) : slapadd -u (essai a blanc) sur le LDIF restaure —
    REJOUABLE, 7 entrees dont uid=sysadmin.
  - bases (data-sql-01) : section forgejo REJOUEE dans une base d'epreuve —
    0 erreur, 130 tables, comptes reels. Production verifiee intacte apres.

Controles negatifs : un LDIF corrompu fait sortir slapadd en 1 ; la garde SQL a
refuse une section mal decoupee.

LE PIEGE pg_dumpall : il ecrit CREATE DATABASE <suivante> AVANT le \connect
correspondant. Decouper « du \connect X au \connect suivant » emporte un ordre
visant une AUTRE base. Ma premiere decoupe l'a fait ; la garde a refuse de
rejouer. Consigne dans runbooks-exploitation.md §5.

valider.yml ne ment plus : il exigeait une restauration de TOUS les noeuds
client_backup, donc echouait sur ceux qui ne detiennent rien et sur les depots
vides. Quatre verdicts desormais (OK / A CONFIRMER / SANS OBJET / ECHEC),
ALIGNES sur ceux de la supervision. Et il prouve que l'annuaire est rejouable,
pas seulement present.

make valider : 0 echec sur toute la flotte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 09:50:51 -04:00

158 lines
7.4 KiB
Markdown

# Runbooks d'exploitation — Set-OPS
> **Pour qui :** l'exploitant, face à une situation. Pas le mainteneur, pas l'apprenant.
> Le *pourquoi* vit dans les autres `docs/` ; la version pédagogique, dans le wiki. Ici,
> uniquement le *comment faire*.
>
> Tu viens de reprendre l'écosystème et tu ne sais pas par où entrer ?
> Wiki → **Reprendre l'écosystème**.
---
## 1. Un certificat a expiré / le login SSO est cassé
**Symptôme** : connexion à une app via le SSO échoue *après* le login ; log Grafana/app :
`tls: failed to verify certificate: x509: certificate has expired`.
**Cause fréquente** : le cert step-ca a été **renouvelé sur disque** mais le service (nginx sur
l'edge) **n'a pas été rechargé** → il sert l'ancien cert en mémoire.
**Diagnostic-réflexe** — comparer le cert *servi* au cert *fichier* :
```bash
# SERVI (en mémoire par nginx)
echo | openssl s_client -connect infra-edge-01…:443 -servername keycloak.lab… 2>/dev/null \
| openssl x509 -noout -enddate
# FICHIER (sur disque)
openssl x509 -in /etc/step/certs/infra-edge-01….crt -noout -enddate
```
Dates différentes (servi < fichier) nginx sert un cert périmé.
**Fix immédiat** : `systemctl reload nginx` sur l'edge (idem `postfix`/`dovecot`/`slapd` selon le
service touché).
**Fix permanent (déjà en place)** : `client_pki_reload_services` recharge les vrais consommateurs
après chaque renouvellement edgenginx, mailpostfix/dovecot, annuaireslapd. Vérifier :
`systemctl cat cert-renewer@$(hostname -f).service | grep ExecStartPost`.
Voir aussi l'unité wiki *PKI & confiance* (⑤ le renouvellement).
---
## 2. Donner Explore/Editor à un opérateur dans Grafana (RBAC)
Par défaut, un utilisateur SSO est **Viewer** (dashboards seulement, pas Explore). Pour l'élever :
1. Déclarer l'assignation dans l'inventaire de l'instance (group_vars `serveur_keycloak`) :
```yaml
serveur_keycloak_role_assignments:
- { user: <uid>, role: grafana-editor } # ou grafana-admin
```
2. Redéployer Keycloak (`make deployer` sur le nœud SSO) — kcadm **à chaud, sans coupure**.
3. L'utilisateur doit **se déconnecter/reconnecter** (Grafana applique le rôle à la connexion).
Mapping (défaut du rôle grafana) : `grafana-admin`→Admin, `grafana-editor`→Editor, sinon Viewer
(`serveur_grafana_oidc_role_path`). Idéal souverain : piloter par un **groupe d'annuaire** plutôt
qu'un utilisateur explicite. Voir l'unité wiki *Autorisation & RBAC*.
---
## 3. « Connection timed out during banner exchange » — le message qui accuse le réseau
**Symptôme** : Ansible marque un ou plusieurs hôtes injoignables, avec ce message. On soupçonne
le réseau, la route ou le pare-feu. **C'est presque toujours autre chose.**
**Pourquoi le message trompe.** À travers la frontière, la poignée TCP aboutit *toujours* — le
pare-feu y répond lui-même sans relayer (voir `frontiere-opnsense.md`). L'échec ne peut donc
pas se présenter comme un « connection refused » franc : il se manifeste plus tard, au moment
où le serveur devrait annoncer sa bannière SSH. D'où un message qui parle de délai réseau pour
un hôte qui, souvent, n'a jamais rien reçu.
**Les trois causes, par fréquence :**
1. **La VM n'est pas encore là.** `make creer-vm` rend la main dès que Proxmox a *démarré* la
VM, pas quand elle répond. C'est le cas normal, quotidien, et aucun correctif ne l'abolira :
les attentes actives du `Makefile` existent pour ça.
2. **`sshd` refuse une rafale.** `MaxStartups` / `MaxSessions` trop serrés coupent des
connexions parfaitement légitimes — deux déploiements interrompus le 2026-08-09, sur des
hôtes qui n'avaient ni redémarré ni perdu leur réseau. Réglés par
`ssh_hardening_max_startups` (`10:30:60`) et `ssh_hardening_max_sessions` (`10`), avec
l'arbitrage expliqué dans `roles/ssh_hardening/defaults/main.yml`.
3. **Le réseau, vraiment** — le cas le plus rare, et le dernier à examiner.
**Ce qui tranche, dans l'ordre :**
```
make hote-afficher HOTE=<nom> # l'hôte existe-t-il au plan ?
ssh -v ansible@<ip> 2>&1 | tail -20 # où la négociation s'arrête exactement
```
Une bannière (`SSH-2.0-OpenSSH…`) prouve que le chemin est bon et que le problème est
applicatif. **Aucun `connect()` ne prouvera quoi que ce soit ici** — il réussit vers le vide.
---
## 4. Brander une instance Forgejo (identité visuelle)
Activer dans l'inventaire (group_vars `serveur_forgejo`) :
```yaml
serveur_forgejo_branding: true
serveur_forgejo_app_name: "Forge Chezlepro"
serveur_forgejo_theme: "forgejo-dark"
serveur_forgejo_meta_description: "…"
```
Puis redéployer. Le rôle déploie le dossier `custom/` officiel (logo/favicon aurore, accent CSS par
variables, page d'accueil brandée) — léger, résistant aux MAJ (aucune classe interne touchée).
Note : ne s'applique qu'aux Forgejo **gérées par Set-OPS**.
## 5. Restaurer — et d'abord : prouver qu'on peut
> Éprouvé le 2026-08-12 sur Chezlepro. `make valider` rejoue la partie automatisable ;
> la restauration d'une base reste manuelle, et porte un piège décrit plus bas.
**Ce que `make valider` prouve tout seul**, pour chaque nœud détenteur d'état : le dernier
instantané se restaure, il en sort des fichiers, et — pour l'annuaire — qu'il est
**rejouable** (`slapadd -u`, essai à blanc, rien n'est écrit). Verdicts possibles :
| Verdict | Sens |
|---|---|
| `OK` | restauré, et non vide |
| `À CONFIRMER` | restauré, mais l'instantané n'emporte **aucun fichier** — légitime si ce nœud n'a pas encore de données, à trancher par un humain |
| `SANS OBJET` | ce nœud ne détient rien de non régénérable |
| `ÉCHEC` | la restauration elle-même a échoué |
### Restaurer les clés de l'autorité (`infra-pki-01`)
```
export RESTIC_REPOSITORY=$(grep -oP 'RESTIC_REPOSITORY="\K[^"]+' /usr/local/sbin/setops-sauvegarder.sh)
export RESTIC_PASSWORD_FILE=/etc/setops/restic.pass
t=$(mktemp -d); restic restore latest --target "$t"
diff -r "$t/etc/step-ca" /etc/step-ca
```
Mesuré : **12 fichiers, 11 identiques octet pour octet**, `root_ca_key` et
`intermediate_ca_key` compris. Le seul écart attendu est `db/000000.vlog` — le journal de
la base badger de step-ca, qui avance à **chaque** émission de certificat.
### Rejouer une base depuis `pg_dumpall` — LE PIÈGE
`pg_dumpall` écrit `CREATE DATABASE <suivante>` **avant** le `\connect` correspondant.
Découper « du `\connect X` au `\connect` suivant » emporte donc un ordre qui vise une
**autre** base. Couper aussi sur `CREATE DATABASE`, et **vérifier avant de rejouer** :
```
awk '/^\\connect forgejo$/{f=1;next} f && (/^\\connect /||/^CREATE DATABASE /){exit} f' \
toutes-bases.sql > section.sql
grep -qE '^(DROP|CREATE|ALTER) DATABASE|^\\connect' section.sql \
&& { echo "REFUS : ordre hors-perimetre"; exit 1; }
runuser -u postgres -- createdb epreuve_restauration
runuser -u postgres -- psql -q -d epreuve_restauration -f section.sql
runuser -u postgres -- psql -tAd epreuve_restauration -c "select count(*) from information_schema.tables where table_schema='public'"
runuser -u postgres -- dropdb epreuve_restauration
```
Mesuré sur `forgejo` : rejeu en **0 erreur**, **130 tables**, et les comptes réels
(`forgejo-admin`, `sysadmin`). Production vérifiée intacte après coup.
**Ne jamais rejouer un `pg_dumpall` entier sur un cluster vivant** : il contient les
`DROP DATABASE` de toutes les bases. Une restauration réelle se fait sur un cluster neuf.