RDP to a Windows server
Connect to a discovered Windows Server from Cushy by brokering RDP through Devolutions Gateway: install the gateway on Windows or Linux, generate the provisioner key pair, and launch a scoped, short-lived session — with no inbound RDP port and no stored passwords.
Cushy can open a Remote Desktop session to a Windows Server it discovered in your cloud account — without exposing port 3389 to the internet and without Cushy ever holding a Windows password. It does this by brokering the session through [Devolutions Gateway](https://github.com/Devolutions/devolutions-gateway), an open-source (Apache-2.0) relay that you run inside your own network.
How it works
- You run a Devolutions Gateway on a host that can reach your Windows servers on tcp/3389, and give it the public half of a key pair.
- You paste the private half into Cushy once. It is encrypted at rest and never returned by the API.
- When you click Connect via RDP, Cushy signs a JSON Web Token that authorizes exactly one destination, one protocol and one session id, for a few minutes.
- Your browser downloads a small
.rdpfile that points at the gateway and carries that token. Remote Desktop (mstsc) or FreeRDP opens it. - The gateway verifies the token against the public key and relays the connection to the server. You authenticate to Windows yourself, as always.
The gateway dials the server's private address (for example 10.0.4.41:3389). A relay hosted by Cushy could not reach it. That is also the security benefit: nothing about your Windows estate is exposed to the internet, and the only thing crossing the boundary is a signed, minutes-long authorization.
Step 1 — generate the provisioner key pair
Do this once, on a trusted machine. RSA 2048+ (RS256, recommended) or EC P-256 (ES256) are supported.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out provisioner.key
openssl rsa -in provisioner.key -pubout -out provisioner.pub.keyprovisioner.pub.key— not a secret. It goes on the gateway.provisioner.key— the secret. It goes into Cushy, once. Delete your local copy afterwards.
Step 2 — install the gateway
On Windows (the common case)
Install the gateway as a Windows service on a management or jump host inside the same network as your servers. Devolutions ships an MSI and a PowerShell configuration module (DevolutionsGateway).
# Install the configuration module
Install-Module -Name DevolutionsGateway -Scope AllUsers
# One-time configuration
Set-DGatewayHostname 'gateway.corp.example'
Set-DGatewayListeners @(
(New-DGatewayListener 'tcp://0.0.0.0:8181' 'tcp://*:8181'),
(New-DGatewayListener 'http://0.0.0.0:7171' 'https://*:7171')
)
# Trust the provisioner: install the PUBLIC half only
Import-DGatewayProvisionerKey -PublicKeyFile .\provisioner.pub.key
Restart-Service DevolutionsGatewayOn Linux (or in a container)
Devolutions publishes .deb/.rpm packages and the devolutions/devolutions-gateway container image. The configuration file lives at /etc/devolutions-gateway/gateway.json:
{
"Id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"Hostname": "gateway.corp.example",
"ProvisionerPublicKeyFile": "/etc/devolutions-gateway/provisioner.pub.key",
"Listeners": [
{ "InternalUrl": "tcp://0.0.0.0:8181", "ExternalUrl": "tcp://*:8181" },
{ "InternalUrl": "http://0.0.0.0:7171", "ExternalUrl": "https://*:7171" }
],
"WebApp": { "Enabled": true, "Authentication": "Custom" }
}A self-hosted Cushy stack ships an opt-in compose profile — docker compose --profile devolutions-gateway up -d devolutions-gateway — with its configuration bind-mounted from deploy/devolutions-gateway/. It is for labs and single-tenant deployments: a gateway running inside the platform's own stack can only reach hosts that stack can reach.
Step 3 — network and firewall
| Direction | Port | Purpose | Who must open it |
|---|---|---|---|
| Operator → gateway | tcp/8181 | The RDP client (mstsc / FreeRDP) follows the downloaded .rdp here | Reachable from wherever your operators sit (VPN, tailnet, office) |
| Cushy → gateway | tcp/7171 (https) | The reachability probe and the browser web client | Only if you want *Check reachability* and the web client to work |
| Gateway → Windows server | tcp/3389 | The relayed session itself | A security-group / NSG rule from the gateway host only |
| Anywhere → Windows server | tcp/3389 | Not required — keep it closed. | — |
The 7171 listener carries the web client and the health endpoint. Put it behind HTTPS (a real certificate, or your reverse proxy). Cushy refuses to store a plain http:// gateway URL unless it is loopback, which is for local development only.
Step 4 — point Cushy at the gateway
- Open any resource from Inventory, then the Access tab.
- Under Devolutions Gateway, choose Configure (you need the *manage cloud accounts* capability — Org Admin or SRE · Cloud Admin).
- Enter the base URL (
https://gateway.corp.example:7171), the RDP listener host and port (8181), optionally the gateway's Id — which pins every token to that one gateway — and the token lifetime (30–900 seconds, default 300). - Paste the contents of
provisioner.keyinto Provisioner PRIVATE key and save. - Use Check reachability to confirm Cushy can see the gateway, and expand Provisioner PUBLIC key to confirm the fingerprint matches the key installed on the gateway.
The private key is encrypted at rest (AES-256-GCM), alongside your SSO client secret and git tokens. It is never returned by any API, never written to a log or an audit entry, and never reaches the browser. Editing the gateway URL later does not require re-pasting it.
Step 5 — connect
On a Windows instance's Access tab you now get Connect via RDP. It downloads a single-session .rdp file — open it with Remote Desktop and sign in to Windows as usual. The token inside is valid only for that one server, that one session and that one short window; downloading again mints a fresh one.
Open web client ↗ launches the gateway's own browser client (IronRDP, no plug-in) in a new tab. Note that it authenticates against the gateway, not Cushy — it mints its own session token there, under whatever WebApp.Authentication mode you configured. It is a convenience, not a Cushy-scoped session.
What you will see when something is missing
| State | What Cushy shows |
|---|---|
| No gateway configured | An honest note, plus a way to configure one (admins) or to ask an admin (everyone else) |
| Gateway configured but no provisioner key | "A session token cannot be signed" — add the key |
| Gateway unreachable from the platform | The *Check reachability* result as information, not an error page — your operators may still reach it even when the platform cannot |
| A Linux instance | "RDP is Windows-only" — SSH and VNC are modelled but not wired yet |
| Operating system not discovered | No RDP action at all — Cushy will not guess an operating system |
| No private or public address discovered | "The gateway has nothing to dial" |
| A pending Terraform draft | "Nothing exists in the cloud to connect to yet" |
Security model
- No inbound RDP. Port 3389 stays closed to everything except the gateway host.
- No credentials in Cushy. The token carries no username or password, and the
.rdpsetsprompt for credentials:i:1. You authenticate to Windows directly. - Least privilege per session. Each token names one destination (
dst_hst), one protocol (jet_ap), one session (jet_aid), a unique id (jti), an expiry measured in minutes and — when you set a gateway Id — one gateway (jet_gw_id). - Scoped to you. The download link is a signed capability pinned to your user and organization; another operator, or another organization, gets a 403.
- Audited.
remote.gateway_configured,remote.gateway_probed,remote.session_startedandremote.session_launchedrecord who connected to what and when. Token values and key material never appear in the audit trail. - Treat the downloaded file as a credential for its short lifetime — it is a bearer capability until it expires.
Current limits
- Windows only. The Linux GUI has its own path — see Linux desktop with KasmVNC. SSH is modelled in the same token seam but is refused with a clear "not wired yet" response.
- One gateway per organization.
- No session recording, no credential injection or vaulting, and no session shadowing.
- The browser web client is a separate sign-in against your gateway.
- A token cannot be revoked before it expires (the gateway's revocation list is not wired yet) — which is why the default lifetime is five minutes.