NetVM: 1:1 profile:node mapping — hashed iface names, netvm-enter + netvm-chrome launcher
This commit is contained in:
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-chrome.sh <profile> [url] — launch a chrome-box profile inside its
|
||||
# dedicated NetVM netns. 1:1: profile = node = warp identity = egress IP.
|
||||
# Run as your normal user (uses sudo -n only for allowlisted netns ops).
|
||||
set -euo pipefail
|
||||
PROFILE="${1:?usage: netvm-chrome.sh <profile> [url]}"
|
||||
URL="${2:-}"
|
||||
NODE="$PROFILE"
|
||||
NETVM_BIN="$(cd "$(dirname "$0")" && pwd)"
|
||||
CHROME_BOX="${CHROME_BOX_BIN:-$HOME/Projects/chrome-box/chrome-box}"
|
||||
[ -x "$CHROME_BOX" ] || { echo "chrome-box not found at $CHROME_BOX (set CHROME_BOX_BIN)"; exit 1; }
|
||||
[ -f "/etc/netvm/${NODE}.conf" ] || { echo "no warp identity for '$NODE' — human: netvm-new-identity.sh $NODE"; exit 1; }
|
||||
sudo -n "$NETVM_BIN/netvm-node-up.sh" "$NODE" | tail -2
|
||||
if [ -n "$URL" ]; then
|
||||
exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "$CHROME_BOX" launch "$PROFILE" "$URL"
|
||||
else
|
||||
exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "$CHROME_BOX" launch "$PROFILE"
|
||||
fi
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# runs as root inside the netns + a private mount ns; drops privs, execs cmd.
|
||||
set -euo pipefail
|
||||
mount --bind "$NETVM_RESOLV" /etc/resolv.conf
|
||||
exec setpriv --reuid="$NETVM_UID" --regid="$NETVM_GID" --clear-groups \
|
||||
env HOME="$NETVM_HOME" "$@"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-enter.sh <node> <uid> <gid> <home> -- <cmd> [args...]
|
||||
# Enter the node's netns, bind a working resolv.conf (host uses the
|
||||
# systemd-resolved stub 127.0.0.53, unreachable in the netns), drop
|
||||
# privileges, exec the command. Run as root (sudo -n via the allowlist).
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/netvm-names.sh"
|
||||
netvm_names "$1"; TUID="$2"; TGID="$3"; THOME="$4"; shift 4
|
||||
[ "${1:-}" = "--" ]; shift
|
||||
RESOLV=/etc/netvm/resolv-warp.conf
|
||||
[ -f "$RESOLV" ] || echo "nameserver 1.1.1.1" > "$RESOLV"
|
||||
ip netns exec "$NETNS" env \
|
||||
NETVM_RESOLV="$RESOLV" NETVM_UID="$TUID" NETVM_GID="$TGID" NETVM_HOME="$THOME" \
|
||||
unshare --mount "$SCRIPT_DIR/netvm-enter-inner.sh" "$@"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
# netvm-names.sh — source this. Sets NETNS WG VETH VPEER SUB GW PEER_IP from NODE.
|
||||
# 1:1 mapping: profile name = node name. Interface names must fit 15 chars,
|
||||
# so wg/veth use a deterministic hash tag; the netns keeps the full name.
|
||||
netvm_names() {
|
||||
NODE="$1"
|
||||
NETNS="warp-${NODE}"
|
||||
_tag=$(echo -n "$NODE" | sha256sum | cut -c1-8)
|
||||
WG="wb-${_tag}"
|
||||
VETH="ve-${_tag}"
|
||||
VPEER="vp-${_tag}"
|
||||
_idx=$(( 0x${_tag:0:3} % 200 + 10 ))
|
||||
GW="10.201.${_idx}.1"; PEER_IP="10.201.${_idx}.2"; SUB="10.201.${_idx}.0/30"
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-node-down.sh <node> — tear down a node's Warp egress. Run as root.
|
||||
set -euo pipefail
|
||||
NODE="${1:?usage: netvm-node-down.sh <node>}"
|
||||
NETNS="warp-${NODE}"
|
||||
VETH="veth-${NODE}"
|
||||
IDX=$(( $(echo -n "$NODE" | cksum | cut -d' ' -f1) % 60 + 10 ))
|
||||
SUB="10.201.${IDX}.0/30"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/netvm-names.sh"
|
||||
netvm_names "${1:?usage: netvm-node-down.sh <node>}"
|
||||
nsexec() { ip netns exec "$NETNS" "$@"; }
|
||||
nsexec ip link del "wg-${NODE}" 2>/dev/null || true # inside netns first
|
||||
nsexec ip link del "$WG" 2>/dev/null || true # inside netns first
|
||||
ip link del "$VETH" 2>/dev/null || true # also drops the netns-side peer
|
||||
ip link del "wg-${NODE}" 2>/dev/null || true # stray host-side copy
|
||||
ip link del "$WG" 2>/dev/null || true # stray host-side copy
|
||||
iptables -t nat -D POSTROUTING -s "$SUB" -j MASQUERADE 2>/dev/null || true
|
||||
ip netns del "$NETNS" 2>/dev/null || true
|
||||
ip link del "wg-${NODE}" 2>/dev/null || true # final sweep post-reap
|
||||
ip link del "$WG" 2>/dev/null || true # final sweep post-reap
|
||||
echo "node=$NODE down"
|
||||
|
||||
+16
-25
@@ -1,26 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-node-up.sh <node> — bring up a node's Warp egress in its own netns.
|
||||
# Run as root (operator: sudo -n via the allowlist).
|
||||
#
|
||||
# Per-node layout:
|
||||
# netns warp-<node>; veth pair veth-<node> <-> vpeer-<node> (10.201.X.0/30)
|
||||
# with host NAT; WireGuard handshake packets route via the veth gateway
|
||||
# (bypassing the tunnel — else they'd loop into it); everything else
|
||||
# defaults through the tunnel.
|
||||
# Run as root (operator: sudo -n via the allowlist). Idempotent.
|
||||
set -euo pipefail
|
||||
NODE="${1:?usage: netvm-node-up.sh <node>}"
|
||||
NETNS="warp-${NODE}"
|
||||
WG="wg-${NODE}"
|
||||
VETH="veth-${NODE}"
|
||||
VPEER="vpeer-${NODE}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/netvm-names.sh"
|
||||
netvm_names "${1:?usage: netvm-node-up.sh <node>}"
|
||||
CONF="/etc/netvm/${NODE}.conf"
|
||||
[ -f "$CONF" ] || { echo "missing $CONF (human: netvm-new-identity.sh $NODE)"; exit 1; }
|
||||
chmod 600 "$CONF"
|
||||
|
||||
# deterministic /30 per node for the veth link
|
||||
IDX=$(( $(echo -n "$NODE" | cksum | cut -d' ' -f1) % 60 + 10 ))
|
||||
GW="10.201.${IDX}.1"; PEER_IP="10.201.${IDX}.2"; SUB="10.201.${IDX}.0/30"
|
||||
|
||||
nsexec() { ip netns exec "$NETNS" "$@"; }
|
||||
|
||||
ip netns add "$NETNS" 2>/dev/null || true
|
||||
@@ -48,14 +35,14 @@ for eip in $(getent ahostsv4 "$ENDPOINT" | awk '{print $1}' | sort -u); do
|
||||
nsexec ip route replace "$eip" via "$GW"
|
||||
done
|
||||
|
||||
# wireguard interface (wg setconf rejects wg-quick extensions: strip them)
|
||||
# wireguard interface (wg setconf rejects wg-quick-only keys: strip them)
|
||||
if ! nsexec ip link show "$WG" >/dev/null 2>&1; then
|
||||
ip link del "$WG" 2>/dev/null || true # stale host-side (reaped zombie netns)
|
||||
ip link add "$WG" type wireguard
|
||||
ip link set "$WG" netns "$NETNS"
|
||||
fi
|
||||
STRIPPED=$(mktemp)
|
||||
grep -vE '^\s*(Address|DNS|MTU|Table|PreUp|PreDown|PostUp|PostDown|SaveConfig)\s*' "$CONF" > "$STRIPPED"
|
||||
grep -vE '^\s*(Address|DNS|MTU|Table|PreUp|PreDown|PostUp|PostDown|SaveConfig)\s*=' "$CONF" > "$STRIPPED"
|
||||
nsexec wg setconf "$WG" "$STRIPPED"
|
||||
rm -f "$STRIPPED"
|
||||
MTU=$(grep -oP '^\s*MTU\s*=\s*\K\d+' "$CONF" | head -1); MTU=${MTU:-1280}
|
||||
@@ -68,13 +55,17 @@ nsexec ip link set "$WG" up
|
||||
nsexec ip route replace default dev "$WG"
|
||||
nsexec ip -6 route replace default dev "$WG" 2>/dev/null || true
|
||||
|
||||
# verify: handshake = tunnel up (definitive); egress IP best-effort (no DNS in netns)
|
||||
sleep 3
|
||||
HS=$(nsexec wg show "$WG" latest-handshakes | awk '{print $2}')
|
||||
# wait for handshake (first one can take ~10s)
|
||||
HS=""
|
||||
for i in $(seq 1 10); do
|
||||
HS=$(nsexec wg show "$WG" latest-handshakes 2>/dev/null | awk '{print $2}')
|
||||
if [ -n "$HS" ] && [ "$HS" != "0" ]; then break; fi
|
||||
sleep 2
|
||||
done
|
||||
if [ -z "$HS" ] || [ "$HS" = "0" ]; then
|
||||
echo "no handshake yet (endpoint=$ENDPOINT) — may still be negotiating"
|
||||
echo "no handshake yet (endpoint=$ENDPOINT)"
|
||||
else
|
||||
echo "handshake ok"
|
||||
fi
|
||||
EGRESS=$(nsexec curl -sk --max-time 15 'https://[2606:4700:4700::1111]/cdn-cgi/trace' 2>/dev/null | grep -oP '^ip=\K.*' || true)
|
||||
echo "node=$NODE netns=$NETNS veth=$SUB egress=${EGRESS:-unknown}"
|
||||
EGRESS=$(nsexec curl -sk --max-time 15 'https://1.1.1.1/cdn-cgi/trace' 2>/dev/null | grep -oP '^ip=\K.*' || true)
|
||||
echo "node=$NODE netns=$NETNS ifaces=$WG/$VETH egress=${EGRESS:-unknown}"
|
||||
|
||||
@@ -32,7 +32,9 @@ sudo tee "$SUDOERS_FILE" > /dev/null << SUDOERS
|
||||
# NetVM operator lifecycle access — managed by netvm-provision-edge.sh. Named zz- so it sorts last: in sudoers the LAST matching entry wins, and this must beat any blanket (ALL) grant.
|
||||
# Lets the operator user bring node egress up/down and read topology.
|
||||
# WireGuard configs in /etc/netvm stay root-only; these scripts never print them.
|
||||
$OPERATOR_USER ALL=(root) NOPASSWD: $REPO/bin/netvm-node-up.sh *, $REPO/bin/netvm-node-down.sh *, $REPO/bin/netvm-topology.sh
|
||||
$OPERATOR_USER ALL=(root) NOPASSWD: $REPO/bin/netvm-node-up.sh *, $REPO/bin/netvm-node-down.sh *, $REPO/bin/netvm-topology.sh, $REPO/bin/netvm-enter.sh *
|
||||
# let the display env survive sudo for browser launches inside the netns
|
||||
Defaults!$REPO/bin/netvm-enter.sh env_keep+="DISPLAY WAYLAND_DISPLAY XDG_RUNTIME_DIR XAUTHORITY"
|
||||
SUDOERS
|
||||
sudo chmod 440 "$SUDOERS_FILE"
|
||||
sudo visudo -c -f "$SUDOERS_FILE" > /dev/null && echo "ok: sudoers valid"
|
||||
|
||||
+6
-11
@@ -2,18 +2,13 @@
|
||||
# netvm-topology.sh — live topology + per-node diagnostics.
|
||||
# Run as root (operator: sudo -n via the allowlist).
|
||||
set -u
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/netvm-names.sh"
|
||||
for ns in $(ip netns list 2>/dev/null | awk '{print $1}' | grep '^warp-'); do
|
||||
node="${ns#warp-}"
|
||||
hs=$(ip netns exec "$ns" wg show "wg-${node}" latest-handshakes 2>/dev/null | awk '{print $2}')
|
||||
netvm_names "${ns#warp-}"
|
||||
hs=$(ip netns exec "$ns" wg show "$WG" latest-handshakes 2>/dev/null | awk '{print $2}')
|
||||
[ -z "$hs" ] && hs="none"
|
||||
routes=$(ip netns exec "$ns" ip route 2>/dev/null | tr '\n' '|' )
|
||||
egress=$(ip netns exec "$ns" curl -sk --max-time 10 'https://1.1.1.1/cdn-cgi/trace' 2>/dev/null | grep -oP '^ip=\K.*' || true)
|
||||
printf 'node=%s netns=%s handshake=%s egress=%s\n routes: %s\n' "$node" "$ns" "$hs" "${egress:-?}" "${routes:-none}"
|
||||
printf 'node=%s netns=%s ifaces=%s/%s handshake=%s egress=%s\n' "$NODE" "$ns" "$WG" "$VETH" "$hs" "${egress:-?}"
|
||||
done
|
||||
# host-side NAT rules for our subnets
|
||||
iptables -t nat -L POSTROUTING -n 2>/dev/null | grep '10.201\.' || echo "no netvm NAT rules on host"
|
||||
|
||||
echo "--- host links ---"
|
||||
ip link show 2>/dev/null | grep -E 'veth-|vpeer-|wg-' || echo "(no netvm links on host)"
|
||||
echo "--- all netns ---"
|
||||
ip netns list 2>/dev/null || echo "(none)"
|
||||
iptables -t nat -L POSTROUTING -n 2>/dev/null | grep '10.201\.' || echo "(no netvm NAT rules on host)"
|
||||
|
||||
Reference in New Issue
Block a user