- configuration.md: [vault_shared]/[vault_local] backends, the DR rationale, user_credentials_default_scope, and the VAULT_SHARED/LOCAL_SECRET_ID env vars. - credential-variables.md: Credentials nav link, scroll/filter/collapsible editor, resumable focused prompt, needed-pill + banner, and the shared/local per-credential scope with its outage trade-off. - migration.md: vault-migrate runbook (dry-run, copy, cutover ordering). - CLAUDE.md: multi-Vault backend summary under the Vault section.
8.4 KiB
CLAUDE.md — Project state for rustguac
What this project is
rustguac is a lightweight Rust replacement for the Apache Guacamole Java webapp. It proxies the Guacamole protocol over WebSockets between web browsers and guacd (the C daemon from guacamole-server). Supports SSH, RDP, VNC, web browser sessions (headless Chromium on Xvnc), and VDI desktop containers (Docker).
Architecture
- Rust binary (
rustguac) — axum web server, session manager, WebSocket proxy - guacd — built from apache/guacamole-server source, handles SSH/VNC/RDP protocol translation
- Xvnc + Chromium — spawned per web-browser session, streamed via VNC through guacd
- Docker — VDI containers spawned per-user, connected via RDP through guacd
Key files
src/main.rs— entry point, CLI (clap), server setupsrc/api.rs— REST API endpoints (session CRUD, recordings, admin)src/session.rs— session state machine, SessionManagersrc/browser.rs— Xvnc + Chromium process lifecycle (display allocator, per-session profile dirs)src/vdi/mod.rs— VdiDriver trait, container types (ContainerSpec, ContainerInfo, ManagedContainer)src/vdi/docker.rs— Docker-based VDI driver (bollard, unix socket, start/reuse/stop)src/guacd.rs— TCP connection to guacd, Guacamole protocol handshakesrc/protocol.rs— Guacamole wire format parser/encodersrc/websocket.rs— WebSocket <-> guacd TCP bridge, recording teesrc/config.rs— TOML config loading with defaultssrc/auth.rs— API key auth middleware (SHA-256, IP allowlists, expiry), role systemsrc/oidc.rs— OIDC authentication (login, callback, logout, group extraction)src/vault.rs— Vault/OpenBao KV v2 client for connections (AppRole auth, token renewal)src/db.rs— SQLite admin database (rusqlite, bundled)static/client.html— Guacamole JS client with auto-scaling displaystatic/connections.html— Vault-backed connections UI (folder/entry management, connect)static/recordings.html— recording playback with auto-scalingstatic/sessions.html— session management dashboarddev.sh— development script (build guacd, run, deps)install.sh— bare-metal Debian 13 installer (systemd services)Dockerfile— multi-stage build (guacd + rustguac + runtime)
Configuration
TOML config file (config.local.toml for dev, --config flag for production). Key settings: listen_addr, guacd_addr, recording_path, static_path, db_path, xvnc_path, chromium_path, display_range_start/end.
Vault / Connections
Optional [vault] section enables the Vault-backed connections. Connection entries (SSH/RDP/Web) are stored in Vault KV v2 — credentials never touch disk or the browser.
[vault]
addr = "https://vault.example.com:8200"
mount = "secret" # KV v2 mount (default)
base_path = "rustguac" # base path under mount (default)
role_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# namespace = "my-ns" # optional, for Vault Enterprise / OpenBao namespaces
# instance_name = "prod-1" # optional, enables instance-scoped entries
VAULT_SECRET_ID env var provides the AppRole secret ID.
Vault KV v2 path structure:
<base_path>/shared/<folder>/<entry>— shared across all instances<base_path>/instance/<name>/<folder>/<entry>— instance-specific<folder>/.config— folder metadata:{"allowed_groups":["group1"], "description":"..."}<base_path>/users/<sanitized_email>— per-user credential variables
Multiple Vault backends (DR)
Optional [vault_shared] / [vault_local] blocks (same keys as [vault]) give
the shared / instance scopes their own Vault so one being down can't take the
other with it. A bare [vault] is unchanged (shared+local both alias it). Secret
IDs: VAULT_SECRET_ID, VAULT_SHARED_SECRET_ID, VAULT_LOCAL_SECRET_ID. Each
backend connects/retries/renews independently; a down backend greys that scope in
the UI. Per-credential scope: a credential variable can be stored shared or local
(location = truth), toggled per-row in My Credentials (hidden with a single
Vault); user_credentials_default_scope (default local) seeds new ones. Split
an existing single-Vault deployment with rustguac vault-migrate (copy subtree +
.config, then add the block + restart — routing is single-source, no read
fallback). Implemented on branch feature/multi-vault-dr (see project memory).
OIDC
Optional [oidc] section enables OpenID Connect authentication. Key settings: issuer_url, client_id, client_secret, redirect_uri. OIDC_CLIENT_SECRET env var can override the config value. groups_claim (default: "groups") specifies the JWT claim for group memberships. extra_scopes requests additional scopes.
Roles
4-tier role hierarchy: admin (4) > poweruser (3) > operator (2) > viewer (1).
- admin: full access, connections folder/entry management
- poweruser: ad-hoc session creation + connections connect
- operator: connections connect only (no ad-hoc sessions)
- viewer: read-only
Deployment
- Bare metal:
sudo ./install.shon Debian 13. Installs to/opt/rustguac, createsrustguacsystem user with home dir, sets up systemd services. - Docker:
docker build -t rustguac .— multi-stage, debian:trixie-slim runtime. - Remote test machine: See project memory for connection details. Binary at
/opt/rustguac/bin/rustguac, config at/opt/rustguac/config.toml.
Build notes
- guacd is built from
../guacamole-server(apache/guacamole-server) - Debian 13 ships freerdp3-dev, not freerdp2-dev. guacamole-server 1.6.1+ has FreeRDP 3 auto-detection. Building with
--with-rdp. - Patches required: guacamole-server needs patches for FreeRDP 3.15+ (Debian 13). See
patches/README.md. All build scripts apply these automatically. - Chromium on headless VMs needs:
--in-process-gpu,--use-gl=angle,--use-angle=swiftshader,--disable-gpu-*,--disable-dev-shm-usage - The
rustguacsystem user MUST have a real home directory (/home/rustguac) or Chromium's crashpad crashes withtrap int3. - Each Chromium session gets an isolated
--user-data-dirto avoid profile lock conflicts.
guacamole-server patches
The patches/ directory contains patches applied to guacamole-server before building. These fix:
- Autoconf
-Werrorvs deprecated FreeRDP headers — FreeRDP 3.15 deprecatescodecs_free(), breaking-Werrorcompile tests and cascading into missing feature macros. - Deprecated function pointer API — Replaces
->input->KeyboardEvent()etc. withfreerdp_input_send_keyboard_event()safe API. - NULL deref in display channel — FreeRDP 3.x fires PubSub events before
guac_rdp_dispis allocated.
To add a new patch: edit ../guacamole-server, export with git diff > patches/NNN-description.patch.
Session types
- SSH — connects guacd directly to target SSH server
- RDP — connects guacd directly to target RDP server (same pattern as SSH, no browser spawning)
- VNC — connects guacd directly to target VNC server
- Web — spawns Xvnc + Chromium, guacd connects via VNC to local Xvnc display
- VDI — spawns Docker container with xrdp, guacd connects via RDP to container port 3389
VDI (Docker containers)
Ephemeral per-user Docker desktop containers. VdiDriver trait in src/vdi/mod.rs enables downstream forks (JumpboxVDI) to add alternative backends (Nomad, Proxmox).
- Container naming:
rustguac-vdi-{username}(deterministic, one per user) - Lifecycle: created on first connect, persists after disconnect for
idle_timeout_mins, reused on reconnect, destroyed on desktop logout or idle timeout - Credentials: auto-generated per session (username from OIDC, random hex password),
chpasswdupdates on reuse - BYO image: any Docker image with xrdp on port 3389 accepting
VDI_USERNAME/VDI_PASSWORDenv vars - Test image:
contrib/vdi-test-image/(Debian trixie + xrdp + xorgxrdp + xfce4) - Thumbnails: client captures display screenshot every 10s, shown in connections "Active Sessions"
- Config:
[vdi]section —enabled,docker_socket,default_cpu_limit,default_memory_limit,ready_timeout_secs,idle_timeout_mins,allowed_images,home_base - Requires:
rustguacuser indockergroup for socket access
Ports
- 8089: rustguac HTTP/WebSocket
- 4822: guacd
- 6000-6099: Xvnc displays (:100-:199, internal)
Testing
tests/test_browser_session.sh— spawns Xvnc + Chromium, screenshots with xwd/ImageMagick, asserts non-black pixels