feat(netns): bwrap-contained chrome + proton wrapper
- netvm-chrome.sh --contained: bwrap fs jail inside netns (profile home only + vault ro at /tmp/vault, Chromium sandbox stays on) - netvm-proton.sh: run proton-cli as the profile identity in netns + jail (config dir + static binary, PROTON_NO_INPUT=1)
This commit is contained in:
+45
-5
@@ -1,15 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-chrome.sh [--headless] [--cdp-port N|--no-cdp] <profile> [url]
|
||||
# netvm-chrome.sh [--headless] [--cdp-port N|--no-cdp] [--contained] <profile> [url]
|
||||
# Launch a chrome-box profile inside its dedicated NetVM netns.
|
||||
# 1:1: profile = node = warp identity = egress IP.
|
||||
# CDP is on by default (deterministic port) so agents can automate the
|
||||
# session via Playwright/Puppeteer; --headless runs without a display.
|
||||
# --contained adds a bwrap filesystem jail INSIDE the netns (no net unshare):
|
||||
# the browser sees only its own profile home (+ vault read-only). Chromium's
|
||||
# own sandbox stays on (we never pass --no-sandbox to it).
|
||||
# Run as your normal user (uses sudo -n only for allowlisted netns ops).
|
||||
set -euo pipefail
|
||||
NETVM_BIN="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$NETVM_BIN/netvm-names.sh"
|
||||
WAYPIPE=0; HEADLESS=0; CDP_OVERRIDE=""; NO_CDP=0; PROFILE=""; URL=""
|
||||
usage() { echo "usage: netvm-chrome.sh [--waypipe] [--headless] [--cdp-port N|--no-cdp] <profile> [url]"; }
|
||||
WAYPIPE=0; HEADLESS=0; CDP_OVERRIDE=""; NO_CDP=0; PROFILE=""; URL=""; CONTAINED=0
|
||||
usage() { echo "usage: netvm-chrome.sh [--waypipe] [--headless] [--cdp-port N|--no-cdp] [--contained] <profile> [url]"; }
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--waypipe) WAYPIPE=1; shift;;
|
||||
@@ -17,6 +20,7 @@ while [ $# -gt 0 ]; do
|
||||
--cdp-port) CDP_OVERRIDE="$2"; shift 2;;
|
||||
--cdp-port=*) CDP_OVERRIDE="${1#*=}"; shift;;
|
||||
--no-cdp) NO_CDP=1; shift;;
|
||||
--contained) CONTAINED=1; shift;;
|
||||
-h|--help) usage; exit 0;;
|
||||
*) if [ -z "$PROFILE" ]; then PROFILE="$1"; elif [ -z "$URL" ]; then URL="$1";
|
||||
else echo "unexpected: $1"; usage; exit 1; fi; shift;;
|
||||
@@ -51,8 +55,44 @@ LAUNCH_ARGS=(launch "$PROFILE" --no-sandbox)
|
||||
[ -n "$CDP" ] && LAUNCH_ARGS+=(--cdp-port "$CDP")
|
||||
[ -n "$URL" ] && LAUNCH_ARGS+=("$URL")
|
||||
[ -n "$CDP" ] && echo "cdp: http://$PEER_IP:$CDP/json/list (local) | ssh -L $CDP:$PEER_IP:$CDP <user>@<tail-ip> (remote)"
|
||||
CMD=("$CHROME_BOX" "${LAUNCH_ARGS[@]}")
|
||||
if [ "$CONTAINED" = 1 ]; then
|
||||
# Contained mode execs Chromium directly (same flags chrome-box uses for a
|
||||
# native launch) because chrome-box re-resolves its profile dir from $HOME,
|
||||
# which the jail replaces. Direct Warp egress: no proxy flags by design.
|
||||
command -v bwrap >/dev/null 2>&1 || { echo "bwrap not found" >&2; exit 1; }
|
||||
P_HOME="$HOME/.local/share/chrome-box/profiles/$PROFILE"
|
||||
mkdir -p "$P_HOME/.config/chromium"
|
||||
KEYRING="$(python3 -c "import json,sys;print(json.load(open('$P_HOME/config.json')).get('keyring','basic'))" 2>/dev/null || echo basic)"
|
||||
SB_DIR="$(dirname "$CHROME_BOX")"
|
||||
BWRAP=(bwrap --unshare-uts --unshare-ipc --die-with-parent
|
||||
--ro-bind / / --dev /dev --proc /proc --tmpfs /tmp --tmpfs /dev/shm
|
||||
--bind "$P_HOME" "$HOME" --setenv HOME "$HOME" --setenv PATH /usr/bin:/bin)
|
||||
[ -d "$HOME/notes" ] && BWRAP+=(--ro-bind "$HOME/notes" /tmp/vault)
|
||||
[ -f "$SB_DIR/hosts-sandbox.conf" ] && BWRAP+=(--ro-bind "$SB_DIR/hosts-sandbox.conf" /etc/hosts)
|
||||
[ -f "$SB_DIR/nsswitch-sandbox.conf" ] && BWRAP+=(--ro-bind "$SB_DIR/nsswitch-sandbox.conf" /etc/nsswitch.conf)
|
||||
CHROMIUM=(/usr/lib/chromium/chromium
|
||||
"--user-data-dir=$HOME/.config/chromium"
|
||||
"--password-store=$KEYRING"
|
||||
--ozone-platform=x11 --disable-gpu --disable-quic
|
||||
--disable-features=DnsOverHttpsUpgrade,AsyncDns
|
||||
--built-in-dns-client-enabled=false)
|
||||
if [ "$HEADLESS" = 1 ]; then
|
||||
BWRAP+=(--unsetenv DISPLAY --unsetenv WAYLAND_DISPLAY --unsetenv XDG_RUNTIME_DIR)
|
||||
CHROMIUM+=(--headless=new)
|
||||
else
|
||||
[ -d /tmp/.X11-unix ] && BWRAP+=(--ro-bind /tmp/.X11-unix /tmp/.X11-unix)
|
||||
[ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && \
|
||||
BWRAP+=(--ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")
|
||||
[ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR/pulse" ] && BWRAP+=(--bind "$XDG_RUNTIME_DIR/pulse" /run/pulse)
|
||||
[ -n "${XDG_RUNTIME_DIR:-}" ] && [ -S "$XDG_RUNTIME_DIR/bus" ] && BWRAP+=(--ro-bind "$XDG_RUNTIME_DIR/bus" /run/dbus/system_bus_socket)
|
||||
fi
|
||||
[ -n "$CDP" ] && CHROMIUM+=(--remote-debugging-port="$CDP" --remote-allow-origins='*')
|
||||
[ -n "$URL" ] && CHROMIUM+=("$URL")
|
||||
CMD=("${BWRAP[@]}" -- "${CHROMIUM[@]}")
|
||||
fi
|
||||
if [ "$WAYPIPE" = 1 ]; then
|
||||
exec waypipe --compress=lz4 run -- sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "$CHROME_BOX" "${LAUNCH_ARGS[@]}"
|
||||
exec waypipe --compress=lz4 run -- sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "${CMD[@]}"
|
||||
else
|
||||
exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "$CHROME_BOX" "${LAUNCH_ARGS[@]}"
|
||||
exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" -- "${CMD[@]}"
|
||||
fi
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# netvm-proton.sh <profile> -- <proton-cli args...>
|
||||
# Run proton-cli as the profile's Proton identity, inside the profile's netns
|
||||
# (Warp egress) and inside a bwrap filesystem jail.
|
||||
# 1:1:1: profile = node = warp identity = proton-cli profile.
|
||||
# Human creates the session once: proton-cli -p <profile> account login.
|
||||
# (Credential setup itself belongs to pix; see proton-ingest design D6.)
|
||||
set -euo pipefail
|
||||
NETVM_BIN="$(cd "$(dirname "$0")" && pwd)"
|
||||
NODE="${1:?usage: netvm-proton.sh <profile> -- <proton-cli args...>}"; shift
|
||||
[ "${1:-}" = "--" ] && shift
|
||||
[ $# -gt 0 ] || { echo "usage: netvm-proton.sh <profile> -- <proton-cli args...>"; exit 1; }
|
||||
[ -f "/etc/netvm/${NODE}.conf" ] || { echo "no warp identity for '$NODE' (human: netvm-new-identity.sh $NODE)"; exit 1; }
|
||||
command -v bwrap >/dev/null 2>&1 || { echo "bwrap not found" >&2; exit 1; }
|
||||
command -v proton-cli >/dev/null 2>&1 || { echo "proton-cli not found" >&2; exit 1; }
|
||||
|
||||
PCLI_HOME="$HOME/.config/proton-cli"
|
||||
[ -d "$PCLI_HOME" ] || { echo "no proton-cli config dir (human: proton-cli account login)"; exit 1; }
|
||||
PCLI_BIN="$(readlink -f "$(command -v proton-cli)")"
|
||||
[ -x "$PCLI_BIN" ] || { echo "proton-cli binary not executable: $PCLI_BIN"; exit 1; }
|
||||
|
||||
# Jail: whole home is tmpfs except the proton-cli config (rw: sessions refresh,
|
||||
# logs), the resolved static binary (ro), and a scratch tmp. proton-cli needs
|
||||
# nothing else on disk.
|
||||
BWRAP=(bwrap --unshare-uts --unshare-ipc --die-with-parent
|
||||
--ro-bind / / --dev /dev --proc /proc --tmpfs /tmp --tmpfs /dev/shm
|
||||
--tmpfs "$HOME" --dir "$HOME/.config"
|
||||
--bind "$PCLI_HOME" "$HOME/.config/proton-cli"
|
||||
--ro-bind "$PCLI_BIN" /tmp/proton-cli
|
||||
--setenv HOME "$HOME" --setenv PATH /usr/bin:/bin
|
||||
--setenv PROTON_PROFILE "$NODE"
|
||||
--setenv PROTON_NO_INPUT 1
|
||||
--unsetenv DISPLAY --unsetenv WAYLAND_DISPLAY --unsetenv XDG_RUNTIME_DIR)
|
||||
|
||||
exec sudo -n "$NETVM_BIN/netvm-enter.sh" "$NODE" "$(id -u)" "$(id -g)" "$HOME" \
|
||||
-- "${BWRAP[@]}" -- /tmp/proton-cli "$@"
|
||||
Reference in New Issue
Block a user