durcissement: ssh+443 LAN-only sur .24/.27, tailscale coupe, couchdb faux positif corrige (auth 401 mesurée)
This commit is contained in:
@@ -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/<brique>/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/<brique>/dumps/` → **déjà dans le périmètre** de backup.
|
||||
Rollback = supprimer les dumps, aucune brique touchée.
|
||||
@@ -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 <n° de OpenSSH Anywhere>` (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.
|
||||
|
||||
@@ -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 `<!-- GÉNÉRÉ … ts=… -->` 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 '^<!-- GÉNÉRÉ PAR'` (plus dépendant de la position). **Dérive re-prouvée avec la comparaison corrigée** : `compose/jellyfin/docker-compose.yml` `10.9`→`10.8` → `DÉRIVE détectée (8 constats)` + `NOTIFY_OK 200` + `rc=1` ; revert → `aucune dérive (22 conteneurs, 7 constats)` + `rc=0` | `git checkout g9-migration/scripts/11-registry.sh` (compose reverti par sed, aucun conteneur touché) |
|
||||
| 2026-10-12 17:55 | 4.5 | **grille d'exposition validée** avec l'utilisateur, mesurée en entier | C-EX1 : `ufw status numbered` complet (.24 = 25 règles, .27 = 12 règles) | **0 règle posée** (G6 respecté). Mesures nouvelles : `OpenSSH ALLOW IN Anywhere` est présent sur **.24 ET .27** (règle [1] sur les deux) ; `443/tcp Anywhere` sur le G9 est la règle [22] « seul port exposé WAN » de l'edge T4.6, et **il n'existe aucune règle LAN pour 443 sur le G9** → supprimer cette règle couperait aussi `git./cloud./media.` **depuis la maison** ; `tailscaled` = **inactive sur le G9**, actif **seulement sur le pilot** ; le pilot et le G9 sont derrière le **même NAT** (IP publique unique `82.65.198.114`) → ce qui est joignable dépend des **redirects Freebox**, pas de ufw seul ; CouchDB 5984 est **déjà LAN-only** (ufw règle 6), le reste du risque = absence d'auth sur le LAN. 5 décisions rendues + **qui pose le geste** (1, 2, 3, 5 = toi ; 4 = moi sur GO) | document seul ; `git checkout EXPOSITION-INTERNET.md` ; **aucune commande ufw exécutée** |
|
||||
| 2026-10-12 18:10 | 4.5 | **durcissement pare-feu exécuté sur GO explicite** (SSH + 443 LAN-only, Tailscale coupé) | C-EX1 : les 5 décisions sont rendues **et** les 3 gestes réseau posés par l'utilisateur, exécutés par moi avec `scripts/14-firewall-lan-only.sh` | **.24** : `[1] 443/tcp ALLOW IN 192.168.1.0/24` + `[2] 22/tcp ALLOW IN 192.168.1.0/24` insérés **avant** les suppressions, puis suppression de `[1] OpenSSH Anywhere`, `[24] OpenSSH (v6)`, `[22] 443 Anywhere`, `[25] 443 (v6)` → **plus aucune règle `Anywhere` sur le G9** (25 → 23 règles). **.27** : `[1] 22/tcp from 192.168.1.0/24` puis suppression `[1] OpenSSH Anywhere` + `[10] OpenSSH (v6)` ; **règles WAN de l'edge conservées** (`80`, `443`, `51820` v4+v6). **Pilot** : `systemctl disable --now tailscaled` (il n'était actif **que** sur le pilot ; G9 = inactive). Vérifié après : `ssh .24` OK, `ssh .27` OK, `git ls-remote gitea` OK, `https://git.folzocorp.fr/api/v1/version` (LAN) **200**, `https://cloud.folzocorp.fr/` (WAN) **200**, `https://prisme.folzocorp.fr/` **200**. Snapshots `ufw status numbered` conservés avant (25 règles .24 / 17 lignes .27) | rollback : `sudo ufw insert 1 allow proto tcp from any to any port 22` et `… port 443` sur .24 (+ v6), idem 22 sur .27 ; `sudo systemctl enable --now tailscaled` ; snapshots dans `/tmp/ufw-24-before.txt` et `/tmp/ufw-27-before.txt` sur le pilot |
|
||||
| 2026-10-12 18:20 | 4.5 | **faux positif corrigé : CouchDB n'est PAS sans auth** | C-EX1 (exactitude des constats), C-AP1 (fiabilité des sondeurs) | la sonde lisait `GET :5984/` = 200 → lu « sans auth ». Mesure directe : **`GET /_all_databases` = 401** et **`GET /_active_tasks` = 401** ; `compose/g9-queue/docker-compose.yml` déclare bien `COUCHDB_USER` + `COUCHDB_PASSWORD=${COUCHDB_PASSWORD}`. `/` est public **par conception** dans CouchDB. **Décision : ne pas recréer le conteneur** pour publier `127.0.0.1:5984` — gain quasi nul (ufw LAN-only + auth déjà en place) et **casse probable de la sync Obsidian** si le client pointe sur `http://192.168.1.24:5984`. Échange mauvais → statu quo, consigné | aucun (aucun conteneur recréé, aucun fichier modifié sur le G9) |
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# durcissement pare-feu (C-EX1, GO utilisateur 12/10) : SSH et 443 restrictes au LAN.
|
||||
# Ordre sur : la règle LAN est insérée AVANT la suppression de la règle WAN -> aucune
|
||||
# déconnexion possible. Les suppressions se font du numéro le plus haut au plus bas
|
||||
# (les numéros ufw se décalent à chaque suppression).
|
||||
# Usage : sudo ./14-firewall-lan-only.sh [--dry]
|
||||
set -uo pipefail
|
||||
|
||||
LAN="${LAN:-192.168.1.0/24}"
|
||||
DRY=0
|
||||
[[ "${1:-}" == "--dry" ]] && DRY=1
|
||||
run() { if [[ "$DRY" == 1 ]]; then echo "DRY: $*"; else "$@"; fi; }
|
||||
|
||||
ufw_num() { # numéros des règles qui matchent un motif, tri décroissant
|
||||
ufw status numbered | grep -E "$1" | sed -n 's/^[\[ ]*\([0-9]*\)\].*/\1/p' | sort -rn
|
||||
}
|
||||
|
||||
echo "== avant =="
|
||||
ufw status numbered | grep -nE "OpenSSH|443/tcp"
|
||||
|
||||
run ufw insert 1 allow proto tcp from "$LAN" to any port 22 comment "SSH LAN-only (C-EX1 12/10)"
|
||||
run ufw insert 1 allow proto tcp from "$LAN" to any port 443 comment "edge LAN-only (C-EX1 12/10)"
|
||||
|
||||
echo "== insertions faites, règles WAN à supprimer =="
|
||||
ufw status numbered | grep -E "OpenSSH|443/tcp"
|
||||
|
||||
for n in $(ufw_num "OpenSSH.*Anywhere|Anywhere.*OpenSSH"); do
|
||||
echo "-- delete OpenSSH #$n"
|
||||
run ufw --force delete "$n"
|
||||
done
|
||||
for n in $(ufw_num "443/tcp.*Anywhere"); do
|
||||
echo "-- delete 443 WAN #$n"
|
||||
run ufw --force delete "$n"
|
||||
done
|
||||
|
||||
echo "== après =="
|
||||
ufw status numbered | grep -E "OpenSSH|443/tcp"
|
||||
ufw status numbered | grep -c ""
|
||||
Reference in New Issue
Block a user