2026-07-04 17:30:13 -04:00
|
|
|
# Runbooks d'exploitation — Set-OPS
|
|
|
|
|
|
doc : pas de page « ce qui va te mentir » — les pieges ont trouve leur domicile
Trois des cinq pieges restants avaient deja une page depuis que la semaine a
avance : le connect() vers le vide (frontiere-opnsense.md + le controle porte
par frontiere-mesurer), le `make prouver` vert (wiki « Verifier le deploye »),
et le banner exchange. Les deux orphelins — kcadm -s sur une map, grafana-cli
dans une base fantome — s'adressent a qui ECRIT du code, pas a qui reprend
l'exploitation : ils n'ont rien a faire dans un chemin de reprise.
Une page separee aurait redit ce que trois autres disent deja, et aurait donc
vieilli mal — le reproche exact qu'on faisait a sa version longue.
La section ⑤ de « Reprendre l'ecosysteme » porte desormais la REGLE qui les
relie (verifier l'instrument avant d'accuser le composant) et un tableau de
trois lignes qui renvoie chacune a son domicile. Plus de promesse en attente.
Et le banner exchange n'avait AUCUN domicile : le savoir vivait dans un
commentaire du Makefile et trois entrees du CHANGELOG — nulle part ou on le
cherche, exactement le mal que la refonte traite. Ecrit en runbook §3, avec
ses trois causes par frequence et ce qui tranche dans l'ordre.
runbooks-exploitation.md declare aussi son lecteur, comme le veut la
convention validee.
Verifie : chaque renvoi controle un a un, prouver.py 0 (33 OK), plan-recette
inchange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:59:02 -04:00
|
|
|
> **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**.
|
2026-07-04 17:30:13 -04:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 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 — edge→nginx, mail→postfix/dovecot, annuaire→slapd. 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*.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
doc : pas de page « ce qui va te mentir » — les pieges ont trouve leur domicile
Trois des cinq pieges restants avaient deja une page depuis que la semaine a
avance : le connect() vers le vide (frontiere-opnsense.md + le controle porte
par frontiere-mesurer), le `make prouver` vert (wiki « Verifier le deploye »),
et le banner exchange. Les deux orphelins — kcadm -s sur une map, grafana-cli
dans une base fantome — s'adressent a qui ECRIT du code, pas a qui reprend
l'exploitation : ils n'ont rien a faire dans un chemin de reprise.
Une page separee aurait redit ce que trois autres disent deja, et aurait donc
vieilli mal — le reproche exact qu'on faisait a sa version longue.
La section ⑤ de « Reprendre l'ecosysteme » porte desormais la REGLE qui les
relie (verifier l'instrument avant d'accuser le composant) et un tableau de
trois lignes qui renvoie chacune a son domicile. Plus de promesse en attente.
Et le banner exchange n'avait AUCUN domicile : le savoir vivait dans un
commentaire du Makefile et trois entrees du CHANGELOG — nulle part ou on le
cherche, exactement le mal que la refonte traite. Ecrit en runbook §3, avec
ses trois causes par frequence et ce qui tranche dans l'ordre.
runbooks-exploitation.md declare aussi son lecteur, comme le veut la
convention validee.
Verifie : chaque renvoi controle un a un, prouver.py 0 (33 OK), plan-recette
inchange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:59:02 -04:00
|
|
|
## 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)
|
2026-07-04 17:30:13 -04:00
|
|
|
|
|
|
|
|
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**.
|
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
|
|
|
|
|
|
|
|
## 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.
|
runbook : la frontiere porte deux adresses de gestion (transition D-77)
10.17.0.1/24 posee par l'API a cote de 10.0.0.1/24, qui reste active. Ajouter
AVANT de retirer : un point de routage qui change d'adresse d'un coup coupe
simultanement l'exploitant, les commutateurs qui l'ont en passerelle par defaut,
et l'outil qui devait faire la bascule.
Documente l'ordre du reste (poste, commutateurs, hyperviseurs, transit, VXLAN,
stockage, puis retrait) et surtout ce qui n'en depend PAS : le renumerotage du
TENANT est independant — la frontiere route son supernet vers le meme prochain
saut quelle que soit sa propre adresse de gestion.
prouver reste a 1 : P03 signale l'ecart plan/applique, qui est reel jusqu'au
renumerotage du tenant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:43:09 -04:00
|
|
|
|
|
|
|
|
## 6. La frontière porte deux adresses de gestion (transition D-77)
|
|
|
|
|
|
|
|
|
|
> Posée le 2026-08-12 par l'API (`interfaces/vip_settings`, mode `ipalias`). **Ce n'est
|
|
|
|
|
> pas une anomalie** : c'est une bascule d'adressage en cours.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
10.0.0.1/24 adresse historique — TOUJOURS ACTIVE, rien ne l'a quittée
|
|
|
|
|
10.17.0.1/24 adresse cible (D-77 : underlay dans la bande basse du /16 du site)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Ajouter avant de retirer**, jamais l'inverse. Un point de routage qui change d'adresse
|
|
|
|
|
d'un coup coupe simultanément l'exploitant, les commutateurs qui l'ont en passerelle par
|
|
|
|
|
défaut, et l'outil qui devait faire la bascule.
|
|
|
|
|
|
|
|
|
|
### Ce qui reste à déplacer, et dans quel ordre
|
|
|
|
|
|
|
|
|
|
1. le poste de l'exploitant — une seconde adresse `10.17.0.x/24` ;
|
|
|
|
|
2. les commutateurs (`10.0.0.3`, `.4`) et leur `ip default-gateway` ;
|
|
|
|
|
3. l'administration des hyperviseurs (`vmbr0`), l'OOB/IPMI ;
|
|
|
|
|
4. le transit (`10.0.4.x`) et le transport VXLAN (`10.0.5.x`) ;
|
|
|
|
|
5. le stockage vers `192.168.<vlan>.0/24` ;
|
|
|
|
|
6. **en dernier seulement**, retirer `10.0.0.1`.
|
|
|
|
|
|
|
|
|
|
### Ce qui ne dépend PAS de cette bascule
|
|
|
|
|
|
|
|
|
|
**Le renumérotage du tenant** (`10.27` → `10.17`) est **indépendant**. La frontière route
|
|
|
|
|
le supernet du tenant vers le même prochain saut, quelle que soit sa propre adresse de
|
|
|
|
|
gestion : il suffit que la route et les alias suivent. Les deux chantiers peuvent donc
|
|
|
|
|
être menés séparément — et c'est préférable.
|
|
|
|
|
|
|
|
|
|
> **Longueur de préfixe.** Une fois les deux faits, la gestion (`10.17.0.0/24`) vit
|
|
|
|
|
> *à l'intérieur* du supernet du tenant (`10.17.0.0/16`). Aucun conflit : la route
|
|
|
|
|
> connectée du `/24` est plus spécifique que celle du `/16`. C'est la règle du préfixe le
|
|
|
|
|
> plus long, pas une coïncidence.
|