docs: upgrade Gitea 1.25.4 -> 1.27.3 + leçon sur appDefinitions/update

- Backup complet (dump SQL + volume) avant upgrade
- Documente l'incident secondaire (502 temporaire sur git.sans.pub) causé
  par un payload appDefinitions/update incomplet ayant réinitialisé
  containerHttpPort à sa valeur par défaut, et le correctif appliqué
- Ajoute un avertissement dans CLAUDE.md : cette API remplace la config,
  ne la fusionne pas

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Pierre Martin
2026-09-23 01:11:03 +02:00
co-authored by Claude Sonnet 5
parent 66c356848b
commit 2e0ae9a91c
2 changed files with 19 additions and 3 deletions
+3 -1
View File
@@ -93,9 +93,11 @@ Endpoints utiles :
- `GET /user/apps/appDefinitions` → liste toutes les apps avec `envVars`, `volumes`, `ports`, etc.
- `POST /user/apps/appDefinitions/update` (payload : `appName` + les champs à conserver/modifier) → met à jour la config d'une app **sans redéployer l'image** (mais redémarre le service)
⚠️ **`appDefinitions/update` remplace, ne fusionne pas** : un champ absent du payload est réinitialisé à sa valeur par défaut (vécu en prod le 2026-09-23 : `containerHttpPort` oublié → réinitialisé à `80` au lieu de `3000` → 502 sur git.sans.pub pendant plusieurs minutes, cf `incidents/2026-09-14-gitea-cryptominer.md`). **Toujours faire un `GET /user/apps/appDefinitions` juste avant et renvoyer l'objet complet**, en ne modifiant que le(s) champ(s) voulu(s).
## Gitea — détails techniques
- **Containers Docker** (Swarm) : `srv-captain--gitea.1.*` (gitea/gitea:1.25.4) + `srv-captain--gitea-db.1.*` (mysql:5.7)
- **Containers Docker** (Swarm) : `srv-captain--gitea.1.*` (gitea/gitea:1.27.3, mis à jour le 2026-09-23) + `srv-captain--gitea-db.1.*` (mysql:5.7)
- **Volume de données** : `captain--gitea-data` → monté sur `/data` dans le container
- Dépôts bare : `/data/git/repositories/<owner>/<repo>.git`
- Config app : `/data/gitea/conf/app.ini` (contient des secrets en clair : `INTERNAL_TOKEN`, `LFS_JWT_SECRET`, mot de passe MySQL — jamais versionner ce fichier)
+16 -2
View File
@@ -1,6 +1,6 @@
# Incident : mineur de cryptomonnaie via Gitea (packObjectsHook)
- **Statut** : contenu, secrets serveur rotés (2026-09-23). Reste : upgrade Gitea, restriction `/api/internal/*`, mots de passe utilisateurs/tokens/webhooks.
- **Statut** : contenu, secrets serveur rotés et Gitea mis à jour vers 1.27.3 (2026-09-23). Reste : restriction `/api/internal/*`, mots de passe utilisateurs/tokens/webhooks.
- **Date de compromission** : 2026-09-14 ~15:16-15:17
- **Date de détection** : 2026-09-22 (via un `git clone` cassé)
- **Impact** : exécution de code sur le serveur hébergeant Gitea (et potentiellement l'hôte partagé, ~40 services CapRover) ; minage de cryptomonnaie (Monero) actif pendant ~8 jours
@@ -88,9 +88,23 @@ Effectué :
Outillage utilisé : CLI `caprover` (installé localement via `fnm` + `npm install -g caprover`, authentifié avec `caprover login` — voir section CapRover CLI dans `CLAUDE.md`).
## Mise à jour Gitea 1.25.4 → 1.27.3 — effectuée le 2026-09-23
Corrige 15 advisories de sécurité publiées le 2026-08-29 (dont au moins 2 RCE en sévérité haute : bypass d'approbation sur les runners self-hosted, RCE via rendu externe de markup). Aucune ne correspond exactement au vecteur de cet incident (API interne), mais l'upgrade était largement justifiée.
Effectué :
- [x] Backup complet avant upgrade : dump SQL (`mysqldump`, 114 tables) + tarball du volume de données complet (2,1 Go), dans `/home/zuck/backups/gitea-upgrade-20260923005619/` sur le serveur
- [x] Déploiement de `gitea/gitea:1.27.3` via l'API CapRover (`POST /user/apps/appData/gitea` avec `captainDefinitionContent`)
- [x] Démarrage vérifié sans erreur de migration, `Git version: 2.54.0`
### Incident secondaire causé pendant l'upgrade (auto-résolu le jour même)
La mise à jour CapRover de `DB_PASSWD` faite plus tôt (rotation des secrets, cf section dédiée) avait été envoyée avec un payload **incomplet** — le champ `containerHttpPort` n'était pas inclus. CapRover l'a réinitialisé à sa valeur par défaut (`80`) au lieu de conserver `3000` (le port réel utilisé par l'image officielle Gitea). Conséquence : **`git.sans.pub` a répondu en 502 pendant plusieurs minutes** (visible dans les logs nginx avec du trafic réel externe), le temps de diagnostiquer (`connect() failed (111: Connection refused)` vers `10.0.1.x:80`) et de renvoyer un payload complet avec `containerHttpPort: 3000` restauré.
**Leçon** : toute mise à jour via `POST /user/apps/appDefinitions/update` doit **toujours renvoyer l'intégralité des champs de la définition actuelle** (récupérés via un `GET /user/apps/appDefinitions` juste avant), pas seulement le(s) champ(s) qu'on veut changer — l'API ne fait pas de merge partiel, elle remplace.
## Reste à faire
- [ ] Mettre à jour Gitea vers la dernière version patchée (partir de `1.25.4`)
- [ ] Vérifier/restreindre l'accès à `/api/internal/*` au niveau du reverse-proxy (défense en profondeur, même si Gitea patch le bug)
- [ ] Changer les mots de passe des 3 comptes Gitea (pierre, Sans.pub, Duogeeks) par précaution
- [ ] Révoquer et régénérer les 4 tokens d'accès personnels (LML, Piaire CI/Deploy, Piaire, Laptop .npmrc) — penser à mettre à jour partout où ils sont utilisés