docs: Web console (Guacamole replacement) design spec
This commit is contained in:
@@ -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": "<AES-256-GCM ciphertext | null>",
|
||||
"has_private": true,
|
||||
"passphrase_enc": "<AES-256-GCM ciphertext | null>",
|
||||
"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=<id> TOKEN=<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.
|
||||
Reference in New Issue
Block a user