diff --git a/docs/superpowers/specs/2026-07-17-web-console-design.md b/docs/superpowers/specs/2026-07-17-web-console-design.md new file mode 100644 index 0000000..23db066 --- /dev/null +++ b/docs/superpowers/specs/2026-07-17-web-console-design.md @@ -0,0 +1,241 @@ +# Vantage Web Console (Guacamole Replacement) — Design + +**Date:** 2026-07-17 +**Status:** Approved design, pre-implementation + +## Goal + +Add a browser-based remote-access console to Vantage — SSH, RDP, and VNC into +managed servers — as a self-hosted Guacamole replacement. Users select an SSH +key to connect over SSH. RDP targets are reachable from a new Windows agent that +registers the host and reports status. Windows agent ships as an MSI installer +produced by CI. + +## Non-Goals (YAGNI) + +- Session recording / replay (may be added later). +- Native Go RDP implementation (guacd handles protocol translation). +- Per-user Linux/Windows account management from the agent. +- Tunneling console traffic through the agent (direct network path assumed). + +--- + +## Architecture + +``` +Browser (guacamole-common-js, vendored — no CDN) + │ Guacamole protocol over WebSocket + ▼ +Go server: /api/console/tunnel (github.com/wwt/guac) + │ Guacamole protocol over TCP :4822 + ▼ +guacd container (Apache Guacamole daemon) + │ SSH :22 / RDP :3389 / VNC :5900 — direct to target IP + ▼ +Target host (LAN / VPN line-of-sight from server) +``` + +- **Browser:** loads vendored `guacamole-common-js`, renders RDP/VNC display and + SSH terminal. No external CDN (matches existing infra rules). +- **Go server:** exposes a WebSocket tunnel endpoint using `github.com/wwt/guac` + (Go Guacamole tunnel library). No Java `guacamole-client` required. +- **guacd:** new container in `deploy/docker-compose.yml`, bound to the internal + docker network only, reachable by the server on `:4822`. +- **Network path:** guacd connects **directly** to the target IP. Requires the + central server to have network line-of-sight to hosts (homelab LAN / VPN). The + agent's outbound-only guarantee is unchanged — the console path is + server→target, not agent-mediated. + +--- + +## Data Model Changes + +### `keys` — extend to hold private material + +```json +{ + "key_id": "uuid", + "label": "dom-macbook", + "public_key": "ssh-ed25519 AAAA...", + "private_key_enc": "", + "has_private": true, + "passphrase_enc": "", + "fingerprint": "SHA256:...", + "source": "uploaded|generated", + "created_at": "ISODate" +} +``` + +- A key may be created from an uploaded **private+public** pair, upload of a + public key only, or agent generation. +- Agent key generation now also uploads `private_key_enc` (reuses the existing + AES-256 key used for at-rest encryption). Private key no longer stays local + only — it is stored encrypted so the console can reuse it. +- Optional `passphrase_enc` for passphrase-protected private keys. +- Console lists only keys where `has_private = true`. + +### `servers` — extend with console metadata + +```json +{ + "...": "...existing fields...", + "os_type": "linux|windows", + "console_protocols": ["ssh"], + "ssh_port": 22, + "rdp_port": 3389 +} +``` + +- `os_type` set at registration from the agent. +- `console_protocols` lists enabled protocols per server (`ssh`, `rdp`, `vnc`). +- Port fields default to standard ports, overridable in the UI. + +### `console_sessions` — new collection (audit) + +```json +{ + "session_id": "uuid", + "server_id": "uuid", + "protocol": "ssh|rdp|vnc", + "key_id": "uuid | null", + "user": "who opened it", + "started_at": "ISODate", + "ended_at": "ISODate | null", + "client_ip": "string" +} +``` + +--- + +## Session Broker + Connection Flow + +New service: `server/internal/services/console.go`. + +1. Browser `POST /api/console/connect` + `{ server_id, protocol, key_id?, rdp_username?, rdp_password? }`. +2. Broker validates request, loads the server (host IP, port for protocol), + loads the key and **decrypts `private_key_enc` in memory only**. +3. Builds the guacd connection parameter map: + - **SSH:** `hostname`, `port`, `username`, `private-key` (decrypted), + `passphrase` (if any). + - **RDP:** `hostname`, `port`, `username`, `password`, `security=any`, + `ignore-cert=true`. + - **VNC:** `hostname`, `port`, `password`. +4. Creates a `console_sessions` document, returns a short-lived signed session + token. +5. Browser opens WebSocket `/api/console/tunnel?token=…`. The `wwt/guac` handler + validates the token, dials guacd `:4822`, and pipes bytes in both directions. +6. On socket close, the broker sets `ended_at` on the session doc. + +### Security + +- Decrypted private keys and RDP passwords are **never persisted, never logged, + never sent to the browser** — passed only to guacd. +- Session token: short TTL (~60s to open the WebSocket), single-use, + HMAC-signed, bound to the authenticated user. +- guacd is bound to the internal docker network only; not exposed publicly. +- At-rest encryption (`private_key_enc`, `passphrase_enc`) reuses the existing + AES-256 key already used for agent-generated private keys. + +--- + +## Windows Agent + +Same Go codebase as the Linux agent, with a reduced role: **register + +heartbeat + status only**. No `authorized_keys` management (meaningless on +Windows). + +- Build target: `GOOS=windows GOARCH=amd64` → `vantage-agent-windows-amd64.exe`. +- Agent detects OS at registration and sends `os_type=windows`. +- The key-sync loop is disabled on Windows via a runtime OS check (or build tag) + — no `authorized_keys` writes are ever attempted. +- Config file: `C:\ProgramData\vantage\config.yaml`, locked down via ACL to the + equivalent of `0600`. +- Runs as a Windows service via **nssm**. + +--- + +## Windows Installer (MSI) + +Agent ships as a WiX v4 MSI produced in CI. + +- **WiX v4** chosen because it is a dotnet tool that builds MSIs + **cross-platform** — runs on the Linux Gitea act_runner. (Inno Setup is + Windows-only and does not fit the runner.) +- MSI bundles `vantage-agent.exe`, installs it to `C:\Program Files\Vantage\`, + and registers the nssm service (ships nssm or uses a CustomAction). +- Accepts install parameters as MSI properties for silent/headless install: + ``` + msiexec /i vantage-agent.msi /qn SERVERID= TOKEN= SERVERURL=vantage..:9090 + ``` +- GUI install (double-click) prompts for server-id / token / server-url via a + dialog. + +### Two install paths + +1. **Installer direct** — user downloads `vantage-agent.msi`, double-clicks, + fills the dialog. No script required. +2. **PowerShell one-liner** — served dynamically (like the existing bash + `/install`). Script downloads the `.msi`, verifies SHA-256, then runs + `msiexec /qn` with injected `SERVERID` / `TOKEN` / `SERVERURL`. Used by the + copy-paste "Add Server" flow. + +The PowerShell script (`/install.ps1`) steps: +1. Detect arch. +2. Download `vantage-agent.msi` from the latest Gitea `agent/v*` release. +3. Verify SHA-256 against `checksums.txt`. +4. Run `msiexec /i vantage-agent.msi /qn SERVERID=.. TOKEN=.. SERVERURL=..`. + +--- + +## Frontend Routes + +| Route | Change | +| ------------------------- | ------------------------------------------------------------- | +| `/servers` | Show `os_type` badge, enabled console protocols | +| `/servers/[id]` | Add **Connect** button(s) per enabled protocol | +| `/servers/[id]/console` | New — full-screen console (guacamole-common-js), key picker | +| `/servers/new` | Offer Windows (MSI) vs Linux (bash) install instructions | + +Console page: select protocol + SSH key (SSH) or enter RDP credentials, call +`/api/console/connect`, open the tunnel WebSocket, mount the Guacamole client. + +--- + +## CI/CD Changes + +### `agent-release.yml` + +- Add `windows/amd64` build: `vantage-agent-windows-amd64.exe`. +- Add WiX v4 MSI build job → `vantage-agent.msi`. +- Add both to `checksums.txt` and release assets. + +Release assets become: +- `vantage-agent-linux-amd64` +- `vantage-agent-linux-arm64` +- `vantage-agent-windows-amd64.exe` +- `vantage-agent.msi` +- `checksums.txt` + +### `server-deploy.yml` + +- Add guacd service to `deploy/docker-compose.yml` (deployed alongside server). + +--- + +## New Dependencies + +- **Go:** `github.com/wwt/guac` (Guacamole tunnel/WebSocket in Go). +- **Container:** `guacamole/guacd` official image. +- **Frontend:** vendored `guacamole-common-js` (no CDN). +- **CI:** WiX v4 dotnet tool; nssm binary bundled for the MSI. + +--- + +## Open Implementation Notes + +- Confirm `wwt/guac` API surface for connection-parameter passing and token auth + binding during implementation. +- nssm packaging inside MSI: bundle the nssm binary as a payload + CustomAction, + or run `sc.exe`-based service install if nssm proves awkward in WiX. +- ACL hardening of `C:\ProgramData\vantage\config.yaml` in the MSI CustomAction.