NetVM: per-node Warp egress design + lifecycle scripts
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# NetVM nodes
|
||||
|
||||
Registry: which node egresses where. Sharing an egress identity is a
|
||||
designed choice — record the reason.
|
||||
|
||||
| node | netns | warp identity | egress IP | tail IP | shared with / reason |
|
||||
|------|-------|---------------|-----------|---------|----------------------|
|
||||
| laptop (node zero) | — | — | — | — | orchestrator + first node, not yet provisioned |
|
||||
@@ -0,0 +1,95 @@
|
||||
# NetVM
|
||||
|
||||
Fleet networking layer. Every node gets a stable network identity; every
|
||||
byte of automation traffic is attributable, consistent, and boring — the
|
||||
way good citizens look to the rest of the internet.
|
||||
|
||||
## Scope
|
||||
|
||||
1. **Per-node egress identity** — one Warp-backed egress per chrome-box /
|
||||
node, in its own network namespace. Stable egress IP per node, explicit
|
||||
mapping, isolated blast radius.
|
||||
2. **Tailscale fabric** — node addressing, operator SSH, human reachability.
|
||||
3. **Human-handoff links** — the reachable path a human uses to complete
|
||||
CAPTCHA/2FA on a live browser session (original scope, kept).
|
||||
|
||||
NetVM owns networking. chrome-box owns browsers and consumes network from
|
||||
NetVM. email-alert owns notifications. The Account(s) hub owns identity
|
||||
records. Secrets and identities are the human's — agents manage structure
|
||||
and lifecycle only, never credentials.
|
||||
|
||||
## Why per-node egress
|
||||
|
||||
- **Anti-fraud, not evasion.** Providers flag IP-hopping as suspicious. A
|
||||
node that always egresses from the same place looks like what it is: a
|
||||
legitimate machine. Standardizing on Warp is the good-citizen move.
|
||||
- **Blast-radius isolation.** One IP reputation problem affects one node,
|
||||
never the fleet. Nodes deliberately do not all share one egress.
|
||||
- **DevOps standardization.** Every node is provisioned the same way:
|
||||
netns + WireGuard-via-Warp + topology entry. No snowflakes.
|
||||
|
||||
Nodes may share an egress identity deliberately (documented in NODES.md);
|
||||
sharing is a designed topology choice, never an accident.
|
||||
|
||||
## Architecture
|
||||
|
||||
chrome-box profile (client X)
|
||||
-> launched inside netns warp-clientX (NetVM provides the launcher)
|
||||
-> wg interface (Warp WireGuard params, human-generated config)
|
||||
-> Cloudflare edge, stable colo (egress IP in NODES.md)
|
||||
-> internet
|
||||
|
||||
operator / human
|
||||
-> Tailscale tailnet (100.x) (management + Waypipe handoff)
|
||||
-> node tail IP
|
||||
|
||||
Warp runs at the network layer, so the browser needs no proxy config —
|
||||
the whole namespace egresses through Warp.
|
||||
|
||||
### Pattern decision
|
||||
|
||||
Two ways to get per-node Warp egress were considered:
|
||||
|
||||
- **warp-cli proxy mode** (mode proxy + SOCKS5 per node): simpler, but
|
||||
warp-cli talks to a single system daemon (warp-svc) — running N daemons
|
||||
on one host is unverified and fights the service model.
|
||||
- **WireGuard in netns (chosen)**: Warp is WireGuard under the hood. One
|
||||
wg interface per node namespace, config generated once by the human
|
||||
(wgcf or equivalent), no daemon, no D-Bus, fully scriptable. One
|
||||
interface per chrome-box, each independently up/down-able.
|
||||
|
||||
Upgrade path if pool IPs prove too fluid: Cloudflare Zero Trust dedicated
|
||||
egress (true static IPs, paid).
|
||||
|
||||
## Provisioning a node
|
||||
|
||||
Identity creation is the human's job; lifecycle is scriptable:
|
||||
|
||||
1. Human: generate the node's WireGuard config once (wgcf register +
|
||||
wgcf generate, or warp-cli equivalent) and place it at
|
||||
/etc/netvm/<node>.conf (root-owned, 0600). This file is a credential —
|
||||
agents never create, read, or copy it.
|
||||
2. sudo bin/netvm-node-up.sh <node> — creates netns warp-<node>, raises
|
||||
the wg interface inside it, verifies egress, prints the result.
|
||||
3. chrome-box launches the client's Chromium inside that netns.
|
||||
|
||||
Tear down: sudo bin/netvm-node-down.sh <node>.
|
||||
|
||||
## Files
|
||||
|
||||
- bin/netvm-verify.sh — prerequisite checks (ip, wg, netns, tailscale…).
|
||||
- bin/netvm-node-up.sh <node> — bring up a node's egress.
|
||||
- bin/netvm-node-down.sh <node> — tear a node's egress down.
|
||||
- bin/netvm-topology.sh — print the live topology table.
|
||||
- NODES.md — the registry: node -> netns -> Warp identity -> egress IP.
|
||||
|
||||
## Verification checklist
|
||||
|
||||
- [ ] netvm-verify.sh passes on the target host.
|
||||
- [ ] Per-node wg interface comes up inside its netns; egress IP differs
|
||||
per node (or matches the designed sharing in NODES.md).
|
||||
- [ ] Egress IP per node stable over 7 days (same colo pool).
|
||||
- [ ] Chromium launched in the netns reaches the internet via the node's
|
||||
egress (ip netns exec warp-<node> curl ifconfig.me).
|
||||
- [ ] Waypipe handoff to a node over the tailnet shows a live browser.
|
||||
- [ ] One node's egress killed -> other nodes unaffected (blast radius).
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tear down a node's Warp egress. Run as root.
|
||||
set -euo pipefail
|
||||
NODE="${1:?usage: netvm-node-down.sh <node>}"
|
||||
NETNS="warp-${NODE}"
|
||||
ip netns exec "$NETNS" ip link set "wg-${NODE}" down 2>/dev/null || true
|
||||
ip netns del "$NETNS" 2>/dev/null || true
|
||||
echo "node=$NODE down"
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bring up a node's Warp egress. Run as root.
|
||||
# The WireGuard config at /etc/netvm/<node>.conf is human-generated
|
||||
# (a credential). This script manages lifecycle only — it never creates
|
||||
# or copies identities.
|
||||
set -euo pipefail
|
||||
NODE="${1:?usage: netvm-node-up.sh <node>}"
|
||||
NETNS="warp-${NODE}"
|
||||
CONF="/etc/netvm/${NODE}.conf"
|
||||
[ -f "$CONF" ] || { echo "missing $CONF — human generates it once (wgcf), root-owned 0600"; exit 1; }
|
||||
chmod 600 "$CONF"
|
||||
ip netns add "$NETNS" 2>/dev/null || true
|
||||
ip link add "wg-${NODE}" type wireguard 2>/dev/null || true
|
||||
ip link set "wg-${NODE}" netns "$NETNS"
|
||||
ip netns exec "$NETNS" wg setconf "wg-${NODE}" < "$CONF"
|
||||
ip netns exec "$NETNS" ip link set lo up
|
||||
ip netns exec "$NETNS" ip link set "wg-${NODE}" up
|
||||
# NOTE: addresses/routes come from the generated config (wgcf carries them).
|
||||
EGRESS=$(ip netns exec "$NETNS" curl -s --max-time 15 ifconfig.me || true)
|
||||
echo "node=$NODE netns=$NETNS egress=${EGRESS:-UNREACHABLE}"
|
||||
[ -n "$EGRESS" ] || { echo "egress check failed"; exit 1; }
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Live topology: netns -> egress IP. Run as root for netns exec.
|
||||
set -u
|
||||
for ns in $(ip netns list 2>/dev/null | awk '{print $1}' | grep '^warp-'); do
|
||||
node="${ns#warp-}"
|
||||
egress=$(ip netns exec "$ns" curl -s --max-time 10 ifconfig.me 2>/dev/null || echo UNREACHABLE)
|
||||
printf '%-20s %-16s %s\n' "$node" "$ns" "$egress"
|
||||
done
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prerequisite checks for a NetVM host. Reports only; safe for any user.
|
||||
set -u
|
||||
ok=1
|
||||
need() { command -v "$1" >/dev/null 2>&1 && echo "ok: $1" || { echo "MISSING: $1"; ok=0; }; }
|
||||
need ip; need wg; need tailscale; need warp-cli; need waypipe; need chromium; need curl
|
||||
[ "$ok" = 1 ] && echo "ALL CHECKS PASSED" || { echo "CHECKS FAILED"; exit 1; }
|
||||
Reference in New Issue
Block a user