Skip to main content
PathMon

Chapter 22 of 26

RDP via Guacamole

Updated Read the full guide

Known issue (2.0.0). The RDP connection flow has a known bug in PatchMon 2.0.0. Sessions may fail to establish, disconnect early, or return opaque errors in certain environments. A fix is planned for the next release. See Release Notes 2.0.0 for details. If RDP is mission-critical for your rollout, validate the workflow in a staging instance before relying on it in production.

Overview

PatchMon 2.0 lets you open a full RDP session to a Windows host from your browser, with no RDP client installed locally and no inbound RDP port exposed to the outside world. The session travels:

  • From the browser as a Guacamole WebSocket tunnel, into the PatchMon server.
  • From the PatchMon server into a guacd sidecar, which speaks the RDP protocol.
  • From guacd through a short-lived TCP proxy to the host's own PatchMon agent, which forwards to localhost:3389 on the Windows host.

This is a one-time ticketed connection with keyboard, mouse, and clipboard support. Screen size is configurable, and NLA/TLS/legacy RDP is auto-negotiated.

RDP is provided under the remote_access capability module.

Known issue: 2.0.0 RDP bug

Before using RDP in production, read this.

Version 2.0.0 has a known bug in the RDP connection flow that can cause:

  • Sessions to fail handshake with generic errors.
  • Ticket resolution issues that surface as "invalid or expired ticket" on otherwise valid sessions.
  • Early disconnects after a successful handshake under some network conditions.

A fix is planned for the next release. In the meantime:

  • If RDP is critical, stay on the last 1.4.x release that works for you, or retry the session.
  • Always verify the Web SSH Terminal works end-to-end first. It has no such known issue and is a good baseline check for connectivity and agent health.
  • When reporting RDP issues, include the server log lines tagged rdp-ticket and rdp tunnel, and the agent's rdp_proxy_* log lines.

Full context: Release Notes 2.0.0, section Known issues.

Architecture

┌──────────┐  Guacamole protocol over WSS  ┌──────────────────┐   TCP 4822   ┌───────┐  TCP  ┌──────────────────┐   TCP 3389  ┌──────────────┐
│ Browser  │ ───────────────────────────→ │ patchmon-server  │ ───────────→ │ guacd │ ────→ │  Ephemeral port  │ ──────────→ │  Windows     │
│ (guac-   │                              │  (Go binary)     │              │       │       │   on the server  │             │  Agent relay │
│  common- │ ←─────────────────────────── │                  │ ←─────────── │       │ ←──── │  (local listen)  │ ←────────── │  → localhost │
│  js)     │                              │                  │              │       │       │                  │             │    3389      │
└──────────┘                              └──────────────────┘              └───────┘       └──────────────────┘             └──────────────┘
                                                   │                                                 ▲
                                                   │           Agent WebSocket (rdp_proxy_*)         │
                                                   └─────────────────────────────────────────────────┘

Key components:

  • guacd: Apache Guacamole's daemon, shipped as a sidecar container in PatchMon's Docker Compose (guacamole/guacd:1.6.0). Runs on 4822/tcp inside the patchmon-internal network. No public ports.
  • PatchMon server: acts as the Guacamole WebSocket tunnel endpoint and owns the RDP ticket store. It asks the host's agent to set up a local TCP proxy, then hands that proxy to guacd.
  • Agent proxy: on receiving rdp_proxy over its WebSocket, the agent opens a local TCP bridge and forwards bytes between PatchMon and localhost:3389 on the Windows host. Requires integrations.rdp-proxy-enabled: true in the agent config.
  • Windows host: runs the standard Windows RDP service on 127.0.0.1:3389 (bound to localhost via the agent; no inbound exposure needed).

See Installing PatchMon Server on Docker for the sidecar configuration as deployed by the standard compose file. If you run PatchMon without the sidecar, install guacd separately (apt install guacd / yum install guacd) and set GUACD_ADDRESS to point at it.

Permissions and module

Access Requirement
Open an RDP session admin, superadmin, or can_use_remote_access + can_view_hosts on your role
Create RDP ticket for a host can_manage_hosts (needed to see the control on the host detail page in the first place)
Deployment remote_access capability module enabled

Users without the required permission are rejected at POST /auth/rdp-ticket with 403 Access denied.

Prerequisites

Before you can open an RDP session to a host, all of these must be true:

  1. The host is identified as Windows in PatchMon (os_type or expected_platform contains "windows"). Non-Windows hosts are rejected with 400 RDP is only available for Windows hosts.
  2. The host's PatchMon agent is online and connected via its WebSocket.
  3. The agent's config.yml has integrations.rdp-proxy-enabled: true. This setting is not pushable from the server. You edit it on the host and restart the PatchMonAgent service.
  4. RDP is enabled on the Windows host, and the agent's user context can reach localhost:3389. (The default NLA mode is fine; PatchMon negotiates security automatically.)
  5. The PatchMon server is able to reach guacd at its configured address (defaults to 127.0.0.1:4822 or guacd:4822 depending on deployment). If not, RDP ticket creation fails early with 503 guacd is not reachable on the PatchMon server.

Opening an RDP session

  1. Go to Hosts and click the Windows host.
  2. On Host Detail, open the Remote Access area and click Open RDP (or the RDP icon in the toolbar).
  3. Enter the Windows username and password for the account to sign in as.
  4. Optionally adjust the Screen size. Defaults to 1024 × 768. Allowed range is 320–8192 on each axis; values outside this range are clamped.
  5. Click Connect.

The server then:

  • Preflights guacd with a 2-second TCP dial.
  • Asks the rdpproxy to allocate an ephemeral listener (one per session), which is the "port" that guacd will dial into.
  • Sends rdp_proxy to the host's agent, waits up to 12 seconds for the agent to acknowledge with rdp_proxy_connected.
  • Mints an RDP ticket and returns the WebSocket tunnel URL to the browser.
  • The browser opens wss://<patchmon-host>/api/v1/rdp/websocket-tunnel?ticket=…&width=…&height=…, completes the Guacamole handshake, and starts streaming frames.

Once connected you see the Windows sign-in screen (or desktop, if NLA authenticated) in your browser.

Credentials handling

  • Username and password are sent once over HTTPS to POST /auth/rdp-ticket and stored encrypted in the RDP ticket record alongside the session ID, host ID, and screen dimensions.
  • The ticket is single-use and expires quickly (tens of seconds). When guacd consumes it to set up the tunnel, the stored credentials are forwarded to guacd and then to RDP. PatchMon itself does not keep them after the session starts.
  • For environments where Windows asks for a certificate, the default guacd config uses ignore-cert=true to accept the self-signed certificate Windows generates out of the box, matching mstsc.exe behaviour. Hardened per-host overrides are a candidate for a future release.
  • Security mode is negotiated as any, which lets FreeRDP pick the strongest common mode (NLA → TLS → legacy RDP). Hardcoding NLA would break hosts with Negotiate/TLS-only security layers and refuses blank-credential sessions.
  • Empty username and password are allowed (some hosts accept blank-credential sessions) but guacd usually fails handshake in that case; the server logs missing_username_or_password: true so you can spot it in the audit trail.

One-time tickets

RDP tickets work like SSH tickets:

  • 64-character hex string, crypto/rand entropy.
  • Stored in Redis with a short TTL.
  • Consumed atomically on first use by doGuacConnect.
  • Bound to a user ID, a host ID, a proxy session ID, a port, the encrypted credentials, and the requested screen width/height.
  • Validated against the user's current active state. A deactivated user cannot re-use a still-live ticket.

You never see or handle the ticket directly; the UI requests it under the hood when you click Connect.

Keyboard layouts and clipboard

  • Keyboard: the Guacamole client maps the browser's keydown events to scancodes. For most Latin keyboards (en-GB, en-US) this "just works". For non-Latin layouts, match the Windows layout to the browser's. Guacamole has no per-session keyboard-layout selector in PatchMon 2.0.
  • Clipboard: bidirectional text clipboard is supported via Guacamole's native clipboard channel. Copy in Windows, paste in the browser, or vice versa. Rich clipboard (images, file lists) is not supported.
  • Mouse: primary, secondary, and wheel. Mouse-wheel-click middle button is supported.
  • Full-screen: toggle via your browser's F11/fullscreen mode. Guacamole resizes the RDP session to the browser viewport where the host allows dynamic resolution.

Printer redirection, audio, drive mapping, and USB forwarding are not enabled in 2.0.

Session limits

Limit Default Source
Concurrent RDP sessions per server 50 rdpproxy.DefaultMaxSessions
Per-session idle timeout 30 minutes rdpproxy.sessionIdleTimeout
guacd preflight timeout 2 seconds guacdPreflightTimeout
Agent handshake timeout 12 seconds agentHandshakeTimeout

Exceeding the concurrency cap returns 503 Too many concurrent RDP sessions on this server, please try again later.

Disconnecting

  • Manual disconnect: close the browser tab or click the disconnect control in the RDP panel. The server tears the session down, tells the agent to close the TCP bridge, and releases the Redis ticket record.
  • Windows sign-out: the RDP session closes normally; the tunnel stays open for a brief grace period before cleanup.
  • Idle close: after 30 minutes of no data flow the session is killed server-side.

Auditing

Every successful RDP ticket creation fires an rdp_session_started event:

  • Severity: informational.
  • Metadata: host_id, host_name, user_id.
  • Reference: the host record.

Route this event type in Notification Routes and Delivery Log if you want a live audit trail of who is signing into Windows hosts from PatchMon.

Server logs include rdp-ticket and rdp session opened lines with the session ID, user ID, host ID, negotiated security posture, and a missing_username_or_password field. Use these to triage incidents; the session ID ties everything together.

Troubleshooting

Symptom Response from the server Likely cause and fix
guacd is not reachable on the PatchMon server. 503, code: guacd_unavailable The sidecar is not running. Check docker compose ps guacd, or install guacd on the host and set GUACD_ADDRESS.
The PatchMon agent on this host is not connected. 503, code: agent_disconnected Agent is offline. Start / restart the PatchMonAgent service on the host.
The PatchMon agent did not respond to the RDP proxy request in time. 504, code: agent_timeout The agent is connected but its handler is stuck, or blocked by firewall. Check agent logs for rdp_proxy entries.
rdp proxy is not enabled (via rdp-proxy-enabled) 502, code: agent_rdp_disabled Set integrations.rdp-proxy-enabled: true in the agent config.yml and restart the agent.
invalid host 502, code: agent_invalid_host Proxy host format rejected (reserved for future per-target proxies).
connection refused / no route to host on port 3389 502, code: rdp_port_unreachable RDP is not running on the Windows host, or a local firewall blocks localhost:3389. Enable RDP on the host.
RDP is only available for Windows hosts 400 Non-Windows host. Use the Web SSH Terminal instead.
Forbidden: origin not allowed in WebSocket upgrade 403 Your browser's Origin header isn't in PatchMon's CORS_ORIGIN allow-list. Update CORS_ORIGIN (or the dynamic origin resolver) to include your PatchMon URL and restart.
Guacamole handshake fails repeatedly with a valid user and password Check rdp tunnel guacd handshake failed in the server log. This is the 2.0.0 known-issue scenario; consult the release notes and retry.