#!/usr/bin/env python3 """identity-provider.py — Proxy provider implementations. A provider owns one network-identity substrate behind a fixed interface: provision / teardown / cycle / exec / routes / status. All subprocesses go through an injectable run function (same seam as box-fleet-tui gather_*), so command shapes are unit-testable and no test touches netns, sudo, or /etc/netvm. Security boundaries (from the repo's own scripts): - Warp identities generate via netvm-new-identity.sh, which the user explicitly authorized operators to run (see netvm-provision-node.sh header). Generation installs a root-0600 conf and prints nothing. - This code NEVER reads /etc/netvm and never prints key material. Confs are consumed only by root tools (wg setconf inside netns). - CLI-facing output carries emails, labels, and fingerprints only. """ from __future__ import annotations import os import re import subprocess import sys from pathlib import Path from typing import Callable, Dict, List, Optional, Tuple REPO_ROOT = Path(__file__).resolve().parent.parent BIN_DIR = REPO_ROOT / "bin" RunFn = Callable[..., Tuple[int, str]] LABEL_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,22}$") def _run(cmd: List[str], timeout: int = 120) -> Tuple[int, str]: """Run cmd, capture output. Returns (returncode, combined_output).""" try: r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) return r.returncode, ((r.stdout or "") + (r.stderr or "")).strip() except subprocess.TimeoutExpired: return 124, "timed out after %ds: %s" % (timeout, " ".join(cmd)) except OSError as e: return 127, str(e) class ProviderError(RuntimeError): """A provider operation failed (message is safe to show).""" def check_label(label: str) -> str: """Validate a netvm label. Returns it or raises ProviderError.""" if not LABEL_RE.match(label or ""): raise ProviderError( "invalid label %r: lowercase letters, digits, hyphens " "(max 23 chars)" % (label,)) return label class Provider: """Interface every proxy provider implements. Boilerplate subclasses override these with real substrate calls; see WarpProvider.""" name = "base" ready = False def provision(self, label: str, run: Optional[RunFn] = None) -> Dict[str, str]: """Create the network identity + bring it up. Idempotent.""" raise NotImplementedError def teardown(self, label: str, run: Optional[RunFn] = None) -> Dict[str, str]: """Bring the identity's network down (keeps the identity).""" raise NotImplementedError def cycle(self, label: str, run: Optional[RunFn] = None) -> Dict[str, str]: """Rotate to a fresh identity (teardown + new identity + up).""" raise NotImplementedError def exec(self, label: str, cmd: List[str], run: Optional[RunFn] = None) -> Tuple[int, str]: """Run cmd inside the identity's network. Returns (rc, output).""" raise NotImplementedError def routes(self, label: str, run: Optional[RunFn] = None) -> Dict[str, str]: """Read-only route/tunnel status for the identity.""" raise NotImplementedError def status(self, label: str, run: Optional[RunFn] = None) -> Dict[str, str]: """Read-only liveness: conf present, netns up, egress IP.""" raise NotImplementedError class WarpProvider(Provider): """Cloudflare Warp provider on the established warp-* structures. Identity: /etc/netvm/