diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 32eacefa..1d1cf7aa 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -3,6 +3,28 @@ --- +## 2026-07-25 — R-level: per-peer MITM inspection level (off/passive/active/reel) (PR #901, deployed gk2) + +Per-wg-toolbox-peer inspection control on the R3 Go engine (`secubox-toolbox-ng` sbxmitm), with bounded self-service + admin override. Built with subagent-driven-development (8 tasks, two-stage reviews, whole-branch opus review). Merged, deployed live, functionally validated. + +- **Four escalating modes** (`off` < `passive` < `active` < `reel`), effective = `forced ?? clamp(chosen, floor, reel)`. Fail-safe passive; board seed `default=reel` to preserve current behaviour (no surprise global downgrade). Self-service peer authenticated by tunnel identity, bounded by floor, cannot lift a `forced`. +- **Go core** (`cmd/sbxmitm/rlevel.go`): `clampVerdict` wired into `Decide` via `decideForPeer` on BOTH accept paths — **pinned-safe** (a splice-learned host stays splice even in reel; the clamp never forces decryption). `PeerPolicy` joins `wg-peers.json` ip→pubkey, hot-reloads on mtime, fail-safe passive; `nil rlevel` = no-op (parity harness unchanged). +- **`off` = nft**: named set `@rlevel_off` in `table inet wg-toolbox` + `ip saddr @rlevel_off return` as the FIRST rule in the SAME prerouting chain as the DNAT fanout. **A separate nat table with `return` does NOT stop the DNAT** (the kernel walks every nat base chain; only a same-chain return/dnat decides) — a critical bug caught in review and fixed. +- **ctl `sbxmitm-policyctl`** (atomic shadow+mv write + `@rlevel_off` set update + audit), run **DIRECTLY by the portal — no sudo**: `peer-rlevel.json` is portal-owned and the nft set needs only CAP_NET_ADMIN (already granted), so the public captive portal keeps `NoNewPrivileges=true` + minimal caps. **This is strictly better than the NNP=false option first considered** (which would also have needed CAP_SETUID/SETGID/AUDIT_WRITE on a public-facing service). +- **Escalation closed at two levels** (final-review CRITICALs): `_is_public_kbin` (the router also serves the public kbin vhost, DNAT'd L4 past nginx/SSO) AND `_require_admin_source` (rejects a tunnel-peer source). Admin pubkey travels in the body (base64 `/` breaks a path param). Panel uses event delegation (no inline-handler XSS; guard hardened to catch concatenation, not just `${}`). +- **Deploy**: cross-built arm64 (`-mod=vendor`), **rolling** worker@1..4 restart (never all four at once). Validated: `force off` → IP enters `@rlevel_off`, `force null` → removed. Drift note: live api.py is A'-patched ahead of the installed .deb 2.8.7 — the merge rebuilds it to 2.8.8. + +## 2026-07-24 — Transparent `.onion` routing (wg-toolbox+LAN) + WPAD/DHCP autodetect (PR #900, deployed gk2) + +`.onion` now routes end-to-end for wg-toolbox/LAN clients, validated live (real onion → HTTP 200). Consolidated into the existing `secubox-proxypac` (the parallel `feat/tor-onion-pac` branch was a duplicate and was abandoned). SDD 8 tasks + final review. + +- **Transparent `.onion` (primary, no PAC)** for force-routed clients: Tor `TransPort`/`DNSPort` automap + Unbound onion-forward (`local-zone "onion." transparent` + `domain-insecure` — else Unbound answers authoritative NXDOMAIN per RFC 6761 and never forwards; `private-domain` keeps the automap range) + nft dnat `10.192.0.0/10`→`127.0.0.1:9040` for `iif {wg-toolbox, eth2}`. **`route_localnet` must be set PER incoming iface** (`conf.all` doesn't apply to already-created ifaces) — this was the silent blocker; a sysctl.d dropin makes it reboot-persistent for new ifaces. +- **PAC/WPAD (fallback)** with passive role autodetect (master DHCP / DNS-resolver / manual), override `proxypac.toml`, rewritten panel (navbar + status + transparent toggle). Onion seed rule repointed from the unreachable mesh SocksPort (`10.10.0.1`) to the LAN SocksPort via a `__LAN_SOCKS__` placeholder substituted at generation. +- **secubox-tor**: LAN `SocksPort` dropin (**never** a `SocksPolicy` — it's GLOBAL and would break the mesh port; confinement = bind IP + nft) + LAN-IP detect helper + `torctl`. +- **Prod repairs along the way**: `tor@default` had been `failed` since 2026-07-10 (a `HiddenServiceDir` was 0750; Tor needs 0700); LAN DNS was broken after the operator pointed DHCP DNS at the box (Unbound wasn't listening on the LAN IP — added `interface:` + `access-control`). +- **Deploy gotchas**: proxypac was absent from `/etc/secubox/aggregator.toml` → its API 404'd (aggregator mounts only listed modules); role.detect false-positived master on the eye-br0 DHCP (`role="slave"` in the toml corrects it — validating the role override). Client: Firefox needs `network.dns.blockDotOnion=false` + `network.trr.mode=0`. +- **Follow-ups (fallback path, non-blocking)**: HAProxy route for `wpad.gk2` (vhost `listen 80` vs nginx:9080) + strict MIME for `/proxy.pac` on the admin vhost (webui.conf doesn't include secubox.d). + ## 2026-07-23 — rpi400 image: auth runtime deps, `--kiosk` in CI, and a postinst bug that broke every install (branch fix/rpi400-auth-deps-and-kiosk) The Pi 400 image shipped a webui nobody could log into, and no kiosk. Both root-caused; two latent packaging bugs surfaced along the way. diff --git a/.claude/TODO.md b/.claude/TODO.md index 2155c437..ca2dabb8 100644 --- a/.claude/TODO.md +++ b/.claude/TODO.md @@ -1,5 +1,35 @@ # TODO — SecuBox-DEB Backlog -*Mis à jour : 2026-07-23* +*Mis à jour : 2026-07-25* + +--- + +## 🧅 Transparent .onion / proxypac — follow-ups fallback WPAD/PAC (2026-07-25, non-bloquant) + +> Le mécanisme **transparent** (primaire, wg-toolbox+LAN) est déployé et couvre le +> cas réel. Ces items ne concernent que le **fallback PAC/WPAD** (clients non +> force-routés) — pas urgents. + +- [ ] **Route HAProxy pour `wpad.gk2.secubox.in`** : le vhost `wpad-vhost.conf` a + `listen 80`, mais sur gk2 nginx sert sur `:9080` derrière HAProxy (qui possède + `:80`) → `/wpad.dat` renvoie 421. Câbler une route HAProxy `wpad.gk2` → nginx:9080 + (patron public-vhost [[project_sbxwaf_live_routing]]) + `listen 9080` dans le vhost. + La résolution DNS `wpad.gk2` → box marche déjà (record tier2 Unbound). +- [ ] **MIME strict `/proxy.pac` sur le vhost admin** : renvoie `text/html` car + `webui.conf` (vhost admin réel) n'inclut pas `secubox.d/`. Ajouter la `location + = /proxy.pac` (MIME `application/x-ns-proxy-autoconfig`) à `webui.conf`, OU router + via le vhost wpad ci-dessus. +- [ ] **Backport source** : la board a l'`api.py` proxypac patchée + le bind Unbound + LAN + la règle onion repointée en live ; vérifier que le rebuild depuis la branche + mergée (#900) réconcilie tout (aggregator.toml registration incluse). + +## 🎚️ R-level par peer — follow-ups (2026-07-25, non-bloquant) + +- [ ] **Réconcilier le drift .deb** : la live api.py toolbox est patchée A' (ctl + sans sudo, NNP=true préservé) mais le `.deb 2.8.7` installé prédate. Rebuilder + `secubox-toolbox` **2.8.8** depuis master (#901 mergée) + redéployer pour durabilité + (un reinstall du 2.8.7 régresserait vers le chemin sudo qui ne marche pas). +- [ ] **Enforcement `reel` — ban/rewrite temps réel** : v1 honore le block existant ; + les hooks ban/rewrite par-peer restent à étoffer (hors périmètre v1). --- diff --git a/.claude/WIP.md b/.claude/WIP.md index ed6909b6..ff1afc17 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -1,5 +1,39 @@ # WIP — Work In Progress -*Mis à jour : 2026-07-23* +*Mis à jour : 2026-07-25* + +--- + +## ✅ 2026-07-25 : R-level MITM par peer wg-toolbox — off/passive/active/reel (PR #901, déployé gk2) + +Niveau d'inspection MITM **par peer** sur le moteur R3 Go (`sbxmitm`), réglable dynamiquement, self-service borné + override admin. SDD 8 tâches (revue 2 étages + revue finale opus). Mergé, déployé, validé fonctionnellement live. + +- **4 modes croissants** (`off` bypass nft < `passive` splice < `active` déchiffré < `reel` +enforcement). Effectif = `forced ?? clamp(chosen, floor, reel)`. Défaut/fail-safe passive ; **seed board = reel** (préserve le comportement actuel). Self-service peer authentifié par l'identité tunnel (borné par floor, ne lève pas forced). +- **Go** : `clampVerdict` **pinné-safe** (un splice-learned reste splice même en reel) branché au call-site `Decide` (2 chemins d'accept) ; `PeerPolicy` (join wg-peers ip→pubkey, hot-reload, fail-safe passive). +- **`off` = nft same-chain** : named set `@rlevel_off` + `ip saddr @rlevel_off return` **1ère règle de la même chaîne** que le DNAT (une table séparée ne bloque PAS le DNAT — bug noyau corrigé en revue). +- **ctl sans sudo** : le portail lance `sbxmitm-policyctl` directement (fichier portal-owned + CAP_NET_ADMIN déjà accordé) → **NNP=true préservé**, zéro réduction de durcissement (meilleur que le NNP=false initialement envisagé). +- **Escalade fermée** (CRITIQUES de revue finale) : `_is_public_kbin` (le router sert aussi le vhost public kbin, DNAT L4) + `_require_admin_source` (rejette une source peer tunnel). Panneau = délégation d'événements (pas d'inline XSS). + +### ⬜ Next +- **Drift .deb** : la live api.py est patchée A' (no-sudo) mais le .deb 2.8.7 installé prédate → le merge rebuild en 2.8.8 réconcilie. Un reinstall du 2.8.7 régresserait — rebuilder depuis master. +- Enforcement `reel` = block existant honoré ; ban/rewrite temps réel = hooks à étoffer (hors périmètre v1). + +--- + +## ✅ 2026-07-24 : Transparent `.onion` (wg-toolbox+LAN) + WPAD/DHCP autodetect (PR #900, déployé gk2) + +`.onion` route de bout en bout pour les clients wg-toolbox/LAN, validé live (onion réel → HTTP 200). Consolidé dans `secubox-proxypac` (la branche `feat/tor-onion-pac` était un doublon, abandonnée). SDD 8 tâches + revue finale. + +- **Transparent (primaire, sans PAC)** : Tor TransPort/DNSPort automap + Unbound onion-forward (`local-zone transparent` + `domain-insecure`, sinon NXDOMAIN RFC6761) + nft dnat `10.192.0.0/10`→`9040`. **`route_localnet` par iface** (conf.all ne s'applique pas aux ifaces déjà créées) — c'était le bloqueur silencieux ; sysctl.d reboot-persistant. +- **PAC/WPAD (fallback)** : autodétection de rôle passive + panneau réécrit ; règle onion repointée du SocksPort mesh (injoignable) vers le SocksPort LAN. +- **secubox-tor** : SocksPort LAN (**jamais** de SocksPolicy — globale, casserait le port mesh) + torctl. +- **Réparations prod** : `tor@default` down depuis le 10/07 (HiddenServiceDir 0750→0700) ; DNS LAN cassé par le changement DHCP (Unbound bind IP LAN). +- **Gotchas déploiement** : proxypac absent d'`aggregator.toml` → API 404 ; role.detect faux-master (DHCP eye-br0) → `role="slave"`. Client Firefox : `network.dns.blockDotOnion=false` + `trr.mode=0`. + +### ⬜ Next (fallback, non-bloquant) +- Route HAProxy `wpad.gk2` (vhost `listen 80` vs nginx:9080) + MIME strict `/proxy.pac` sur le vhost admin (webui.conf n'inclut pas secubox.d). Le transparent primaire couvre le cas réel. + +### 🧹 Issues clôturées (session 24-25/07) +#900/#901 mergées. Backlog nettoyé : **#471, #494** (parent /run/secubox systémique), **#745** (podcaster socket-race #761), **#735** (autolearn faux-positif), **#765** (lyrionctl status-probe), **#743** (egress hors-Tor #797). **#816** gardé ouvert (fix websockets opérationnel seulement, pas de garde source). ---