From 67b5b68c18f14cea69d944ca22b46b1ded7e6724 Mon Sep 17 00:00:00 2001 From: OpenFox Date: Mon, 12 Oct 2026 20:03:03 +0200 Subject: [PATCH] =?UTF-8?q?durcissement:=20ssh+443=20LAN-only=20sur=20.24/?= =?UTF-8?q?.27,=20tailscale=20coupe,=20couchdb=20faux=20positif=20corrige?= =?UTF-8?q?=20(auth=20401=20mesur=C3=A9e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g9-migration/BACKUP-PERIMETRE.md | 88 ++++++++++++++++++++ g9-migration/EXPOSITION-INTERNET.md | 36 +++++++- g9-migration/journal.md | 2 + g9-migration/scripts/14-firewall-lan-only.sh | 38 +++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 g9-migration/BACKUP-PERIMETRE.md create mode 100644 g9-migration/scripts/14-firewall-lan-only.sh diff --git a/g9-migration/BACKUP-PERIMETRE.md b/g9-migration/BACKUP-PERIMETRE.md new file mode 100644 index 0000000..1487a59 --- /dev/null +++ b/g9-migration/BACKUP-PERIMETRE.md @@ -0,0 +1,88 @@ +# Périmètre de backup 3-2-1 — mesuré (C-P5, 2026-10-12) + +**Ce qui est prouvé**, ce qui **manque**, et ce qui est **exclu par construction**. +Tous les chiffres viennent de commandes, pas d'intention. + +## 1. Ce qui tourne (mesuré) + +| Élément | Valeur mesurée | +|---|---| +| Cible | `rclone:freeboxftp:Freebox/backup/g9` (repo restic **v2**, id `e52f18f4…`) | +| Snapshots | **5**, dernier `06a7e3b8` 2026-10-12 12:05, **541.393 GiB** | +| Sources | `/data` `/home/folzo` `/etc` | +| Rétention | 7 daily / 4 weekly / 12 monthly / 5 yearly (`restic-prune.sh`) | +| Anti-ransomware | rclone en **`--append-only`** sur le backup et le prune (le refus de supprimer des objets est le comportement attendu, journalisé) | +| Cron root | `30 3` backup · `4 4` prune · `15 9` health | +| Exclusions (`/etc/restic/excludes.txt`) | `*/cache/*`, `*/.cache/*`, `*/tmp/*`, `*/.npm/*`, `*/node_modules/.cache/*`, `/home/folzo/watchtower/log/*` | + +## 2. Présence dans le snapshot `latest` (607 284 entrées) + +`restic ls latest` — compté, pas supposé : + +| Brique | Entrées | | +|---|---|---| +| photoprism originals | **505 836** | photos | +| nextcloud | 88 017 | coffre applicatif | +| `gitea/db` (Postgres) | **2 162** | base **incluse** | +| gitea | 5 599 | dépôt git | +| `nextcloud/db` (MariaDB) | **844** | base **incluse** | +| vaultwarden | 630 | **le coffre** | +| `/home/folzo` | 5 054 | n8n, synapse, couchdb, valheim, obsidian (tous en bind `/home/folzo/*`) | +| edge / Caddy | 52 | config + certs | +| jellyfin | 50 | config (médias exclus, voir §4) | + +Les bases sont incluses parce que les conteneurs `nextcloud_db`, `gitea-db`, +`photoprism_db` sont en **bind mount** sous `/data/docker//db` (mesuré par +`docker inspect`) — pas en volumes nommés `docker/volumes`, qui seraient passés hors périmètre. + +## 3. Restauration prouvée + +`scripts/10-verify-restore-photo.sh` (rc=0) : snapshot le plus récent → 505 836 entrées +`/data/docker/photoprism/originals` → restauration de +`Photos_scannees 2007-10-14/selection/Photo20071013181010171.jpg` → **sha256 identique à la +source `.25`** (`00cbfed8da815c0c4517554e5353d3503c70f8dd3f9f617a4440aa8f159d0a6f`). + +C'est la seule restauration testée à ce jour. Elle prouve la chaîne complète : repo chiffré, +clé, rclone, contenu, intégrité. + +## 4. Exclusions + +**Médias 15 To + 5,5 To : exclus par construction**, pas par filtre. Le G9 ne porte que +**564 Go** (`/data/docker` 564 G + `/data/famille` 472 K) ; les médias vivent sur le NAS `.25` +et sur les disques USB de la Phase 6 (`/mnt/usb-media`, hors des sources restic). Rien n'est +donc sauvegardé en double, et rien de volumineux n'est oublié. + +**À assumer explicitement** (à ce jour non chiffré, non sauvegardé, non testé) : les médias +eux-mêmes. La Phase 6 les déplace sur USB ; ils ne rentrent dans aucune des 3 copies tant que +la 2ᵉ/3ᵉ cible n'existe pas. + +## 5. Ce qui manque pour C-P5 (le critère demande 3 copies) + +| Copie | État | +|---|---| +| 1 — G9 → Freebox (quotidien) | **en place et mesuré** | +| 2 — PC IA (quotidien) | **absente** (aucune 2ᵉ destination configurée) | +| 3 — 218play (hebdo) | **absente** — décision utilisateur 12/10 : « pas tout de suite » | + +`restic snapshots` sur les 3 cibles = **1 sur 3**. Le critère reste donc **non satisfait**, et +c'est le seul point dur : il faut les identifiants de la 2ᵉ cible (SMB/rclone) pour la poser. + +## 6. Cohérence applicative (à faire, je ne l'ai pas testé) + +La copie se fait **conteneurs tournants** pour Nextcloud/MariaDB et Gitea/Postgres : le script +ne stoppe que `valheim n8n synapse couchdb obsidian` (`RESTIC_STATEFUL`), pas les bases. +Une copie chaude de `datadir` Postgres/MariaDB n'est **pas garantie cohérente** — la copie est +complète en octets, pas nécessairement rejouable en l'état. + +Recommandation (lecture seule pour vérifier, écriture pour produire) : + +```bash +# Gitea : dump logique versionné avec le dépôt +docker exec gitea-db pg_dump -U gitea gitea | gzip > /data/docker/gitea/dumps/gitea.sql.gz +# Nextcloud : export cohérent via occ +docker exec -u www-data nextcloud php occ db export --type=structure > /data/docker/nextcloud/dumps/nc-structure.sql +docker exec -u www-data nextcloud php occ db export --type=data > /data/docker/nextcloud/dumps/nc-data.sql +``` + +Les dumps tombent sous `/data/docker//dumps/` → **déjà dans le périmètre** de backup. +Rollback = supprimer les dumps, aucune brique touchée. diff --git a/g9-migration/EXPOSITION-INTERNET.md b/g9-migration/EXPOSITION-INTERNET.md index 8e61753..92be975 100644 --- a/g9-migration/EXPOSITION-INTERNET.md +++ b/g9-migration/EXPOSITION-INTERNET.md @@ -1,7 +1,9 @@ -# ÉTUDE EXPOSITION INTERNET (T-D — étude, **aucune règle posée**) +# ÉTUDE EXPOSITION INTERNET (T-D — étude, puis durcissement **autorisé**) -> Périmètre : documenter, pas agir. **0 règle ufw modifiée, 0 changement réseau** (G6). -> Faits mesurés le 12/10/2026 en lecture seule (`ufw status numbered`, `ss -ltnu`, Caddyfile, journal). +> Étude posée le 12/10 **sans aucune règle modifiée** (G6 respecté tant que les décisions +> n'étaient pas rendues). Les décisions sont rendues le 12/10 et le §6 consigne **les gestes +> exécutés après GO explicite de l'utilisateur**, avec avant/après mesurés et rollback. +> Faits mesurés en lecture seule (`ufw status numbered`, `ss -ltnu`, Caddyfile, journal). ## 1. Exposition mesurée aujourd'hui @@ -70,8 +72,34 @@ de ufw seul. ufw ne fait qu'ouvrir la porte une fois le paquet arrivé. | 1 | `22/tcp` ouvert au WAN | « si je ferme, plus de SSH pour moi → garder pour le réseau local » | **Remplacer** la règle `OpenSSH Anywhere` par `22/tcp from 192.168.1.0/24` sur **.24 et .27** (le SSH reste depuis la maison, l'attaque depuis le WAN tombe). Ordre sûr : insérer la règle LAN **avant** de supprimer la règle WAN → aucune déconnexion possible. `sudo ufw insert 1 allow proto tcp from 192.168.1.0/24 to any port 22` puis `sudo ufw delete ` (et les 2 règles v6) | **toi** (G6 : modification réseau) | | 2 | `443/tcp` ouvert au WAN sur le G9 (edge TLS auto-signé) | « je ne sais pas » | **Remplacer, pas supprimer** : il n'existe **aucune** règle LAN pour 443 sur le G9, donc supprimer la règle `[22] Anywhere` couperait aussi `https://git./cloud./media.…` **depuis la maison**. Geste : `sudo ufw insert 1 allow proto tcp from 192.168.1.0/24 to any port 443` puis `sudo ufw delete 22` (et la v6). Le seul edge public reste alors le J5040 (Let's Encrypt) | **toi** | | 3 | Tailscale | « je ne pensais pas l'utiliser, est-ce utile ? » | **Non, inutile au projet** : il n'est actif que sur la **machine de pilotage** (`.24` = `inactive`, aucun nœud Tailscale sur le G9 ni le J5040). C'est une **voie d'entrée distante non documentée** dans S9, avec un agent qui a accès au LAN. L'entrée distante prévue du plan = **WireGuard sur le J5040**. Donc : couper sur le pilot → `sudo systemctl disable --now tailscaled` (rollback : `sudo systemctl enable --now tailscaled`) | **toi** (c'est la machine de pilotage, pas le G9) | -| 4 | CouchDB `5984` en LAN sans auth | « fais ce que tu conseilles » | ufw est déjà **LAN-only** pour 5984 (règle 6) — ce qui reste, c'est que **n'importe quel appareil du LAN** lit et écrit la base de sync d'Obsidian. Les conteneurs qui en ont besoin (Obsidian) passent par le **réseau docker interne**, pas par le port de l'hôte. Donc : publier le port en `127.0.0.1:5984:5984` dans `compose/g9-queue/docker-compose.yml` puis recréer le conteneur. Rollback : remettre `5984:5984` | **moi, sur GO** (recréation d'un conteneur = L3) | +| 4 | CouchDB `5984` | « fais ce que tu conseilles » | **Constat initial faux, corrigé par mesure** : la sonde voyait `GET :5984/` = 200 et l'a lu « sans auth ». Mesure directe : `GET /_all_databases` = **401**, `GET /_active_tasks` = **401** → **l'auth est bien active** (`COUCHDB_USER` + `COUCHDB_PASSWORD` dans `compose/g9-queue/docker-compose.yml`). `/` est public par conception dans CouchDB : c'est un faux positif de la sonde. **Recommandation révisée : ne pas recréer le conteneur.** Passer le port en `127.0.0.1:5984` n'apporte presque rien (ufw est déjà LAN-only, l'auth est en place) et **casse probablement la sync Obsidian** si le client pointe sur `http://192.168.1.24:5984`. Gain marginal / risque réel = mauvais échange. À faire plus tard **seulement** si tu confirmes que le client Obsidian n'utilise pas le port de l'hôte. | **personne** (statu quo mesuré) | | 5 | Périmètre « joignable sans le vouloir » | « idem, au mieux » | **Validé tel quel**, avec l'ajout mesuré : `OpenSSH Anywhere` est présent sur **.24 et .27** (pas seulement .24), et l'inventaire des **redirects Freebox** reste à faire côté Freebox par toi — c'est le seul trou de visibilité qui subsiste | **toi** (lecture de l'interface Freebox) | **Aucune règle n'est posée par moi** (G6). Les lignes 1, 2, 3, 5 sont des commandes que **tu** exécutes ; la ligne 4 est la seule que j'exécute, et seulement sur GO explicite. + +## 6. Actions posées le 2026-10-12 (GO explicite de l'utilisateur) + +`scripts/14-firewall-lan-only.sh` (insertion LAN **avant** suppression WAN, suppressions du +numéro le plus haut au plus bas, `--dry` exécuté avant l'exécution réelle). + +| # | Geste | Avant (mesuré) | Après (mesuré) | +|---|---|---|---| +| 1 | SSH LAN-only sur **.24** | `[1] OpenSSH ALLOW IN Anywhere` + `[24] OpenSSH (v6) Anywhere` | `[2] 22/tcp ALLOW IN 192.168.1.0/24` — **plus aucune règle `Anywhere`** sur le G9 (23 règles) | +| 1 | SSH LAN-only sur **.27** | `[1] OpenSSH Anywhere` + `[10] OpenSSH (v6)` | `[1] 22/tcp ALLOW IN 192.168.1.0/24` — les règles WAN de l'edge (`80`, `443`, `51820` v4+v6) **conservées** | +| 2 | 443 LAN-only sur **.24** | `[22] 443/tcp Anywhere # edge T4.6` + v6 | `[1] 443/tcp ALLOW IN 192.168.1.0/24` ; le seul edge public restant = **J5040** (Let's Encrypt) | +| 3 | Tailscale sur le pilot | `tailscaled` actif, `tailscale0` UP, 6+ pairs (`ai-computer`, macbook, vps, windows, chronos) | `inactive`, service désactivé au boot. Vérifié **après** : SSH `.24` OK, SSH `.27` OK, `git ls-remote gitea` OK | +| 4 | CouchDB | — | **statu quo** (voir §5, ligne 4 : auth mesurée = 401, faux positif de sonde) | + +**Vérifications après bascule** : `ssh .24` = OK, `ssh .27` = OK, `https://git.folzocorp.fr/api/v1/version` +(LAN, via l'edge G9) = **200**, `https://cloud.folzocorp.fr/` (WAN, via J5040) = **200**, +`https://prisme.folzocorp.fr/` = **200**. + +**Rollback** : snapshots `ufw status numbered` conservés avant les changements (25 règles .24, +17 lignes .27) ; rétablir une règle WAN = `sudo ufw insert 1 allow proto tcp from any to any port 22` +(idem 443) ; Tailscale = `sudo systemctl enable --now tailscaled`. Journal 18:10. + +**Ce que ça ne règle pas** : ce qui est joignable depuis l'extérieur reste gouverné par les +**redirects de la Freebox** (IP publique unique `82.65.198.114`, G9 et pilot derrière le même +NAT). L'inventaire des redirects se fait côté Freebox, par toi — c'est le seul trou de +visibilité qui subsiste. diff --git a/g9-migration/journal.md b/g9-migration/journal.md index bc30311..17c4523 100644 --- a/g9-migration/journal.md +++ b/g9-migration/journal.md @@ -148,3 +148,5 @@ | 2026-10-12 17:25 | 4.5 | **compose du kit versionnés** : les briques déclarées hors git sont rentrées dans le dépôt | C-ID1 (fiabilité du registre), C-ID5 : la colonne « kit » était fausse (9 conteneurs marqués hors kit) | import dans `g9-migration/compose/` des définitions réelles qui vivaient hors kit : `matrix/` (`/home/folzo/matrix`), `g9-queue/` (`/home/folzo/docker-compose.yml` : n8n, watchtower, couchdb, obsidian), `valheim/`, `diun/`, + `edge-j5040/Caddyfile` ; **0 secret importé** (les `secrets/` du G9 sont en 700 root, les compose ne contiennent que des `${VAR}` et des chemins `secrets_file`) ; les fichiers du kit déjà versionnés **ne sont pas écrasés** (la copie live n'écrase pas le kit : la dérive doit rester visible). Constats automatiques **14 → 7** ; restent 7 vrais constats : 4 images `:latest` non épinglées (n8n, element, synapse, obsidian), `g9/valheim` dont le projet compose s'appelle `compose`, `j5040/sweet_feynman` sans projet compose et sans label watchtower | `git rm -r g9-migration/compose/{matrix,g9-queue,valheim,diun}` (aucun conteneur touché) | | 2026-10-12 17:35 | 4.5 | bug **fausse dérive permanente** corrigé dans `11-registry.sh --check` | C-ID5 (fiabilité) : `--check` répondait DÉRIVE alors que rien n'avait bougé | la ligne `` est à la **ligne 9** (elle suit le bloc de citations d'en-tête), et la comparaison retirait les lignes **1 à 4** → l'horodatage restait dans le corps comparé = **dérive à chaque passage**. Retrait par motif `grep -v '^