Add RDP & VNC remote desktop support (in-browser + native VNC) with session recording#1987
Add RDP & VNC remote desktop support (in-browser + native VNC) with session recording#1987theharold wants to merge 15 commits into
Conversation
Add the VNC protocol to the core data model so it can be configured and stored, ahead of the protocol implementation: - TargetVncOptions + VncTargetAuth (None/Password) and TargetOptions::Vnc - TargetKind::Vnc and the From<&TargetOptions> mapping - VncConfig (enable/listen/external_port/external_host) on WarpgateConfigStore - vnc field on UserRequireCredentialsPolicy + per-protocol policy wiring - vnc in PortsInfo/ExternalHostsInfo info API - default VNC port/listen (5900)
- warpgate-core: protocol-agnostic DesktopEvent/DesktopInput/DesktopRect/ DesktopState types shared by all graphical protocols (VNC, later RDP) and by the native + in-browser paths - warpgate-protocol-vnc: backend VNC client built on vnc-rs that connects to a target, authenticates, decodes the framebuffer to normalised DesktopEvents (BGRA), and forwards DesktopInput as RFB input events
- warpgate-web-desktop crate (mirrors warpgate-web-ssh): WebDesktopClientManager, session with reconnect buffer, WS protocol (JSON control + base64 framebuffer), and ws_handler. Reuses the warpgate-protocol-vnc backend client and normalised DesktopEvent/DesktopInput stream - warpgate-protocol-http: /web-desktop/sessions REST API (authorize_target + per-user ownership checks) and the /web-desktop/sessions/:id/stream WebSocket route, wired alongside the existing web-ssh endpoints
- admin: VNC option in ChooseTargetKind/CreateTarget, vnc/Options.svelte editor (host/port/auth) wired into Target.svelte - gateway: route VNC targets to a new in-browser desktop viewer (WebDesktop.svelte) that renders the framebuffer to a <canvas> over the /web-desktop WebSocket and forwards pointer/keyboard input - regenerate admin + gateway OpenAPI schemas for the new VNC target options and web-desktop endpoints
Lets native VNC viewers connect through Warpgate (like the SSH proxy): - VncProtocolServer (ProtocolServer) + run_server, gated on config.store.vnc.enable - Warpgate acts as an RFB server offering only VeNCrypt X509Plain: the viewer authenticates over TLS with a full-length user:target username + password, reusing the standard auth_state_store/validate_credential/authorize_target flow (and the ticket selector), exactly like the MySQL/Postgres proxies - backend RFB client handshake (None or VNC Auth via DES) to the target, then a transparent bidirectional relay - VncConfig gains certificate/key (for the VeNCrypt TLS cert); config-schema.json regenerated
Mirror of the VNC foundation for RDP: - TargetRdpOptions (host/port/username/domain) + RdpTargetAuth (password) and TargetOptions::Rdp - TargetKind::Rdp and the From<&TargetOptions> mapping - RdpConfig (enable/listen/external_port/external_host/certificate/key) on the config store - rdp field on UserRequireCredentialsPolicy + per-protocol policy wiring - rdp in PortsInfo/ExternalHostsInfo - default RDP port/listen (3389); regenerated config + OpenAPI schemas
…owser path IronRDP's CredSSP stack (sspi/picky) exact-pins RustCrypto pre-release crates that conflict irreconcilably with russh's pins in a shared lockfile (no russh/ ironrdp version pair aligns; [patch] can't fix =-vs-= clashes). Resolved by building RDP as a standalone helper binary with its own lockfile (the guacd model): - warpgate-rdp-helper: standalone binary (excluded from the workspace) that connects to an RDP target via IronRDP (TLS + NLA/CredSSP), decodes the framebuffer, and speaks line-delimited JSON over stdio (config in, framebuffer out as base64 BGRA, input incl. mouse + keysym->scancode/Unicode keyboard) - warpgate-protocol-rdp: in-workspace crate (no IronRDP deps) that spawns the helper and bridges its stdio to the shared DesktopEvent/DesktopInput streams - web-desktop manager dispatches VNC and RDP targets uniformly - admin RDP target editor + portal routing to the in-browser desktop viewer Native RDP acceptor (NLA server for native mstsc clients) is future work.
Add session recording for in-browser desktop (RDP/VNC) sessions, reusing the existing recording infrastructure: - RecordingKind::Desktop + DesktopRecorder (warpgate-core/src/recordings/desktop.rs): serialises the normalised DesktopEvent framebuffer stream as timestamped JSONL whose shape mirrors the web-desktop ServerMessage; DesktopRecordingMetadata carries protocol (vnc/rdp) + target - web-desktop manager taps every DesktopEvent into the recorder before pushing to the browser; recording is a no-op when disabled - admin API: GET /recordings/:id/desktop (streams the file) and a live WS /recordings/:id/desktop-stream, mounted alongside the existing routes - frontend: shared common/desktopCanvas.ts renderer (extracted from WebDesktop, reused by both live client and player); DesktopRecordingPlayer.svelte with play/pause/seek + live tail; wired into Recording.svelte + recordings.ts - README + docs-drafts/desktop-recording.md (external-site content) Limitations: only in-browser sessions are recorded (native VNC proxy is a transparent relay and can't be decoded); seeking replays from the start.
|
🧪 Call for testers! This is a sizeable feature spanning new protocols and a graphical pipeline, and I'd really appreciate extra eyes and hands-on testing before merge. If you have an RDP or VNC target handy, please give it a spin and report back:
Feedback on input handling (keyboard layouts/modifiers, mouse), rendering correctness, performance, and the auth/routing flows would all be very welcome. Bug reports with the target type/version + Warpgate logs are 🙏. Thanks! |
…support - deny.toml: allow the Unlicense license scoped to async_io_stream (a permissive public-domain dependency pulled in transitively by vnc-rs), which was failing cargo-deny's license check - check-schema-compatibility: add an oasdiff severity-levels file that treats additive response changes (new oneOf union members / new enum values) as informational rather than breaking. Adding RDP/VNC target options and the Desktop recording kind only *adds* variants, which is backward-compatible for the lockstep-generated client - rfb.rs: document that the DES usage is mandated by the RFB VNC Authentication security type (challenge-response only, not used for confidentiality), to explain the accepted CodeQL weak-crypto finding
|
Pushed
Unrelated follow-up noted in the description: the RDP helper currently skips target-certificate verification — I'll make that configurable in a follow-up. |
|
Just letting you know that I saw this, massive work - but will take me at least until next week to start reviewing 🤝 |
|
Thank you @Eugeny |
|
I do have a branch I made privately to try to do RDP, it works with native RDP, not the web browser and doesn't support VNC. It also does recording. I was working on cleaning it up to submit as a PR when I saw this already open today. I very much want to get native RDP working, not just through the web browser. Should I wait for this to be merged in and build off of this foundation for native RDP usage? |
| for (i, b) in password.bytes().take(8).enumerate() { | ||
| key[i] = b.reverse_bits(); | ||
| } | ||
| let cipher = Des::new(&key.into()); |
Summary
Adds remote desktop support to Warpgate — addressing #894 (RDP) and extending it to VNC — plus session recording for those sessions.
user:targetusername and your Warpgate password (reuses the existing auth state / ticket flow, exactly like the MySQL/Postgres proxies).How it works
warpgate-protocol-vnc(native VeNCrypt listener + backend client) andwarpgate-protocol-rdp(subprocess bridge) crates. Both normalise their backend into a sharedDesktopEventstream consumed by a newwarpgate-web-desktopcrate (mirrorswarpgate-web-ssh) and rendered bygateway/WebDesktop.svelte.warpgate-rdp-helper, intentionally kept outside the cargo workspace: IronRDP's CredSSP stack (sspi/picky) exact-pins RustCrypto pre-release crates that conflict irreconcilably withrussh's pins in a single lockfile (norussh/IronRDP version pair aligns them, and[patch]can't fix=-vs-=clashes). The helper has its own lockfile and talks to Warpgate over stdio — the same isolation approach Apache Guacamole uses withguacd.RecordingKind::Desktop); the admin player replays it on a canvas and can tail a live session.Limitations / notes
mstsc) is future work.cd warpgate-rdp-helper && cargo build) and located at runtime viaPATHor theWARPGATE_RDP_HELPERenv var.docs-drafts/desktop-recording.mdcontains ready-to-paste content for the docs site.Trying it out
cargo build --workspace; the RDP helpercd warpgate-rdp-helper && cargo build; frontendcd warpgate-web && yarn && yarn build.vnc.enable+ a TLS cert/key in config.user:target.recordings.enable: true, start a session, then open it under the session in the admin UI to play back / watch live.Closes #894