Cloud VMs: private per-user Freestyle VPC + WireGuard tunnel from the cmux app

Merged
JA
2mo ago
+2857-52

What

Cloud VMs no longer expose any public port. Every Freestyle machine joins the one private VPC that belongs to its owner, and the owner's computers join the same VPC over WireGuard tunnels that the cmux app sets up — so the cmux-tui daemon is reachable only from the owner's own machines and computers.

Backend

  • One VPC per (user, provider), provisioned idempotently on first machine create (services/vms/privateNetwork.ts, cloud_vm_networks); hashed slugs so provider-side listings don't leak user ids.
  • Machines (ad-hoc createVm and Base open/reset/reopen) attach to the VPC at create and state outbound-only firewall rules — no public → 1337 rule at all. The VPC's single members-reach-each-other rule is what admits the owner's tunnels and other machines.
  • Attach routes resolve to the machine's private VPC address (ws://[fd…]:1337/v1/link), never falling back to public for a VPC machine.
  • POST/GET/DELETE /api/vm/tunnel: WireGuard tunnel enrollment per (user, device). The client generates the keypair and sends only the public half; a mismatched key rotates the tunnel in place, keeping the device's address on the network. Deliberately not Pro-gated: a lapsed subscription must still reach machines it owns.
  • Account deletion deletes provider tunnels + the VPC after machines are destroyed.
  • Rollback: CMUX_VM_PRIVATE_NETWORK_ENABLED=0 reverts later creates to the public-IPv6 posture; existing machines keep working either way because reachability is resolved from the addresses each machine actually holds.

Mac app + CLI

  • VMTunnelManager: per-installation device id + Curve25519 keypair (private key never leaves ~/.cmuxterm/wireguard, 0600), idempotent enrollment on demand, completed wg-quick config, root-free liveness detection (interface addresses, since wg-quick's name file is root-only on macOS).
  • cmux vpn up|down|status|revoke over new vm.tunnel_* socket verbs — the shipping bring-up path via sudo wg-quick (the NetworkExtension path is gated at runtime on the packet-tunnel entitlement and will take over with no CLI change once release signing carries it).
  • New user-facing strings localized (en/ja).

Fixes found in dogfood

  • Base machines were the only ones left publicly exposed (base create path skipped network placement).
  • vpn up hung: Foundation Process detaches its child's process group, so sudo's tty read got SIGTTIN. Foregrounded like the feed TUI does.
  • Enrollment approve loop now stops on 404 (machine destroyed mid-setup previously flooded the control plane for 5 minutes).
  • Stats on providers without getStats now answer typed 501 (was retryable 502 → infinite panel polling).
  • remove_blaxel migration made fresh-replay-safe (enum literal → text comparison).

Verified

  • 14 new backend tests (vm-private-network.test.ts) + updated Freestyle provider tests; full affected suite green (247 tests).
  • 9 new Swift VMTunnelManagerTests (keypair stability, config completion, liveness parsing), wired into the test target.
  • Live end-to-end: from a Mac with no public IPv6, enrolled via the app, cmux vpn up, created machines that landed in the VPC with zero public inbound rules, and drove their terminals through the WireGuard tunnel (typed a command over the cmux-remote link and read the output back). The old public-IPv6 path was unreachable from that network — exactly the failure this removes.

Notes for reviewers

  • Freestyle devbox snapshot: local verification used cmux-devbox-20260902b (sh-749d7644…) via FREESTYLE_SANDBOX_SNAPSHOT; the manifest still needs that entry recorded for deployed environments.
  • Follow-up: the activity panel still polls stats after the 501; client-side stop is a small separate change.
  • The NetworkExtension entitlement work (app-managed tunnel, no sudo) is scaffolded behind VMTunnelManager.networkExtensionAvailable() and intentionally not part of this PR.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Cloud VMs no longer expose any public port. Every Freestyle machine joins its owner's private VPC, and the owner's computers reach it through a WireGuard tunnel set up by the cmux app, so the cmux-tui daemon is reachable only from the owner's own machines and computers.

Backend

  • One VPC per (user, provider) is provisioned idempotently on first machine create.
  • Machines attach at their private VPC address with outbound-only firewall rules; no public inbound port at all.
  • POST/GET/DELETE /api/vm/tunnel enrolls WireGuard tunnels per (user, device); the client sends only the public half of its keypair, and a mismatched key rotates the tunnel in place.
  • Tunnel enrollment is deliberately not Pro-gated so a lapsed subscription can still reach machines it owns.
  • Account deletion removes tunnels and the VPC after machines are destroyed.
  • CMUX_VM_PRIVATE_NETWORK_ENABLED=0 reverts later creates to the public-IPv6 posture; existing machines keep working either way.
  • Base machines now join the owner's network too, closing the last public-exposure path.
  • Stats on providers without getStats answer 501 instead of retryable 502; the remove_blaxel migration is replay-safe, and the private-network migration is ordered after the E2B/Daytona provider removal so fresh databases apply cleanly.

Mac app + CLI

  • VMTunnelManager mints a stable per-installation device id and Curve25519 keypair and writes the completed wg-quick config to ~/.cmuxterm/wireguard/cmux.conf.
  • cmux vpn up|down|status|revoke brings the tunnel up via sudo wg-quick; the NetworkExtension path is gated at runtime and will take over with no CLI change once release signing carries the entitlement.
  • vpn up no longer hangs because the child's process group is foregrounded so sudo can prompt.
  • The enrollment approve loop stops on 404 instead of flooding the control plane when a machine is destroyed mid-setup.

Summary by CodeRabbit

  • New Features
    • Added cmux vpn commands to enroll, connect, disconnect, inspect, and revoke private VM network tunnels.
    • Cloud VMs now support private networking through WireGuard, with secure device enrollment and tunnel management.
    • Added tunnel status details, JSON output, key rotation, and localized CLI messages.
  • Bug Fixes
    • Improved handling when a cloud machine is deleted during enrollment.
    • Account deletion now cleans up associated private networks and tunnels.
  • Documentation
    • Updated VM and VPN help to explain private network access and setup.