fix(toolbox): wg LXC privileged mode + listen-host env var, netns finding (ref #498)

Continuation of the wg LXC migration. Resolved the unprivileged-LXC
bind-mount denial that blocked 2.3.1 by switching the wg target to
PRIVILEGED LXC (matches the existing mitmproxy WAF LXC convention),
and parameterised the launcher's listen-host so the LXC can bind
0.0.0.0 on its br-lxc interface.

Live cutover attempt then surfaced a deeper architectural issue :

  mitmproxy transparent mode uses Linux's SO_ORIGINAL_DST socket
  option to recover the original destination IP/port. SO_ORIGINAL_DST
  is conntrack-backed, and conntrack is namespace-scoped. The DNAT
  entries created by the host's `nft prerouting` chain are invisible
  inside the LXC's netns. Every flow errors with :

      Transparent mode failure: FileNotFoundError(2, 'No such file or
      directory')

  and gets RST'd after the TLS ClientHello.

Two viable architectures going forward, documented inline in the
LXC conf template :

  1. Keep mitm-wg on the host (current path — the host service was
     never disabled in source, only at runtime, and the rollback
     re-enabled it ; the iPhone tunnel is back on the host mitm-wg).
  2. Switch the LXC to `mitmproxy --mode wireguard` — mitmproxy
     BECOMES the WG endpoint, no DNAT needed, conntrack stays in
     the LXC netns. Requires migrating wg-peers.json to mitm's
     server.conf format + moving UDP 51820 ingress (host nft DNAT
     instead of wg-quick on host).

The LXC infrastructure (privileged template, env-driven launcher,
provisioner) stays shipped as scaffolding for the option-2 migration.
Live state of gk2 host : back on host mitm-wg, iPhone tunnel rx
84 MB / tx 733 MB, growing normally.
This commit is contained in:
CyberMind-FR 2026-06-08 09:41:54 +02:00
parent f1418b5311
commit c0fc354597
5 changed files with 79 additions and 7 deletions

View File

@ -1,3 +1,29 @@
secubox-toolbox (2.3.2-1~bookworm1) bookworm; urgency=medium
* Phase 7 follow-up (#498) — wg LXC : privileged mode + listen-host
parameterisation, but transparent-mode cutover blocked by netns
architecture.
- secubox-toolbox-lxc-provision : new `wg` target now produces
a PRIVILEGED LXC (drops lxc.idmap, drops lxc.include userns.conf,
matches the existing mitmproxy WAF LXC convention). Solves the
CA-WG bind-mount permission denial from 2.3.1.
- secubox-toolbox-mitm-wg-launch : new env var
MITM_WG_LISTEN_HOST overrides the hardcoded 10.99.1.1 so the
LXC variant can bind 0.0.0.0 on its br-lxc interface.
- lxc/toolbox-mitm-wg.service.template : sets
Environment=MITM_WG_LISTEN_HOST=0.0.0.0 alongside RuntimeMaxSec.
- lxc/toolbox-mitm-wg.conf.template : documents the netns/conntrack
finding that blocks the transparent-mode cutover live. Two
viable paths forward (keep host or switch to mitmproxy --mode
wireguard) recorded inline.
Cutover attempted live 2026-06-08, traffic reached the LXC, TLS
handshake failed because SO_ORIGINAL_DST returns FileNotFoundError
(conntrack is netns-scoped). Rolled back to host service ; iPhone
tunnel never broke for more than a few seconds. The LXC stays
provisioned + ready as a base for the --mode wireguard migration.
-- Gérald Kerma <devel@cybermind.fr> lun., 08 juin 2026 07:41:54 +0000
secubox-toolbox (2.3.1-1~bookworm1) bookworm; urgency=medium
* Fix (#498) : NetworkManager connection `id` was the raw User-Agent

View File

@ -8,6 +8,29 @@
# This is the SECOND mitm container — first being toolbox-mitm (#495
# captive transparent). Architecture invariant : the two MUST stay
# disjoint — distinct CA, distinct addon chain, distinct log dir.
#
# ┌─────────────────────────────────────────────────────────────────────┐
# │ KNOWN LIMITATION (Phase 7 #498 cutover attempt 2026-06-08) │
# │ │
# │ Transparent mode in this LXC FAILS when DNAT happens in the host │
# │ netns. mitmproxy 11+ uses Linux's SO_ORIGINAL_DST socket option │
# │ (conntrack-backed) to recover the original destination IP/port. │
# │ conntrack is namespace-scoped — the entries created by the host's │
# │ `nft prerouting` chain are invisible inside the LXC's netns, so │
# │ mitmproxy errors with `Transparent mode failure: │
# │ FileNotFoundError(2, 'No such file or directory')` on every flow. │
# │ │
# │ Two viable architectures going forward : │
# │ 1. Keep mitm-wg on the host (current path until #498 resolves). │
# │ 2. Switch the LXC to `mitmproxy --mode wireguard` — mitmproxy │
# │ BECOMES the WG endpoint, no DNAT needed, conntrack stays in │
# │ the LXC netns. Requires migrating wg-peers.json to mitm's │
# │ server.conf format + moving UDP 51820 ingress to the LXC IP. │
# │ │
# │ This file + the provisioner stay shipped as scaffolding for the │
# │ option-2 migration ; the host service `secubox-toolbox-mitm-wg` │
# │ remains the active path. │
# └─────────────────────────────────────────────────────────────────────┘
# Base — minimal Debian 12 rootfs, /opt/mitmproxy-toolbox (mitmproxy 11+)
# bind-mounted in from the host so the LXC inherits whatever version the

View File

@ -33,6 +33,12 @@ Type=simple
User=root
WorkingDirectory=/usr/lib/secubox/toolbox
# Phase 7 (#498) — LXC sees DNAT'd traffic arriving on the br-lxc
# interface (eth0 inside the container, 10.100.0.62). Bind 0.0.0.0
# so we don't have to know our IP at startup. The launcher reads this
# env var and passes it via --listen-host.
Environment="MITM_WG_LISTEN_HOST=0.0.0.0"
# Same launcher as the host variant — it is bind-mounted in from the host
# (lxc.mount.entry in toolbox-mitm-wg.conf). The launcher composes the
# ignore_hosts regex from static + dynamic bypass lists and exec's

View File

@ -105,9 +105,23 @@ done
lxc-info -n "$CONTAINER" 2>/dev/null | grep -q 'State.*RUNNING' \
&& lxc-stop -n "$CONTAINER" -t 5 -k 2>/dev/null && sleep 1 || true
# Extract the idmap lines from the auto-generated config (these encode
# the chosen unprivileged uid range — must NOT be re-randomised).
IDMAP_LINES=$(grep -E '^lxc\.idmap' "$LIVE_CONFIG" || true)
# Phase 7 (#498) — privilege mode :
# transparent (Phase 5) → unprivileged (lxc.idmap preserved from
# auto-gen) — matches the captive-mitm
# container convention.
# wg → PRIVILEGED — required for the bind mount
# of /etc/secubox/toolbox/ca-wg (0750
# root:secubox-toolbox) which is invisible
# to a non-privileged LXC's mapped UID.
# Matches the existing mitmproxy WAF LXC.
if [ "$TARGET" = "wg" ]; then
IDMAP_LINES=""
INCLUDE_USERNS=""
log "wg target → PRIVILEGED LXC (no idmap, matches mitmproxy WAF convention)"
else
IDMAP_LINES=$(grep -E '^lxc\.idmap' "$LIVE_CONFIG" || true)
INCLUDE_USERNS="lxc.include = /usr/share/lxc/config/userns.conf"
fi
# Compose final config :
TMP_CONFIG=$(mktemp)
@ -116,10 +130,9 @@ TMP_CONFIG=$(mktemp)
echo "# DO NOT EDIT — re-run the provisioner to refresh."
echo
echo "lxc.include = /usr/share/lxc/config/common.conf"
echo "lxc.include = /usr/share/lxc/config/userns.conf"
echo
echo "$IDMAP_LINES"
[ -n "$INCLUDE_USERNS" ] && echo "$INCLUDE_USERNS"
echo
[ -n "$IDMAP_LINES" ] && { echo "$IDMAP_LINES"; echo; }
echo "lxc.rootfs.path = dir:${LIVE_ROOTFS}"
# Then everything from the template EXCEPT lxc.rootfs.path (we just
# set the correct one above) and any include directives that the

View File

@ -42,9 +42,13 @@ if [ -n "$PATTERNS" ]; then
fi
# ── Build mitm args ──
# Phase 7 (#498) — listen-host is overridable via env. Host (default) binds
# 10.99.1.1 (the wg-toolbox interface IP) ; LXC variant sets 0.0.0.0 so it
# accepts the DNAT'd traffic on the 10.100.0.62 br-lxc interface.
MITM_WG_LISTEN_HOST="${MITM_WG_LISTEN_HOST:-10.99.1.1}"
ARGS=(
--mode transparent
--listen-host 10.99.1.1
--listen-host "$MITM_WG_LISTEN_HOST"
--listen-port 8081
--set confdir=/etc/secubox/toolbox/ca-wg
--set ssl_insecure=false