Running a Remote Desktop Gateway
I am a heavy user of Remote Desktop. I connect to my work computer from my MacBook for coding, debugging, doing demos, sometimes even gaming. In some cases, I connect even from my phone. For remote access, I am using a combination of either Apache Guacamole in the browser (it’s amazing, but there are some frustrating things like copying images to session or some shortcuts - Windows X MacOS), Cloudflare Access in combination with a native RDP application (either MSTSC or the Windows app) or sometimes cloudflared in combination with Bastion setup. Recently, I was looking into hosting my own Remote Desktop Gateway to be able to connect to a machine behind a firewall without the need to be in a VPN (Remote Desktop Gateway masks the RDP traffic as HTTPS connection).
In the past I have done many Remote Desktop Service deployments, some of which are still running to date. The first requirement however is, I don’t want to host a Windows Server. Second, I don’t want to manage an Active Directory either (it is a requirement). Third, I preferably want to deploy this on my existing server and preferrably as a Docker container.
So I started searching around and found out that there are a few (not really well known) implementations of the Remote Desktop Gateway and its protocol:
- https://github.com/mKenfenheuer/rdpgw.net/ (C#, also has a working sample)
- https://github.com/bolkedebruin/rdpgw (Go)
- https://github.com/gabriel-sztejnworcel/pyrdgw (Python)
All of these libraries seem to implement it in a similar fashion, however I found the RDPGW in Go the most complete - it also ships as a Docker container, which you can just use - the only issue is that the documentation is somewhat not complete and some things are not really working - yet.
With this implementation, you can choose between openid, local (Basic authentication), ntlm, headers (when behind Cloudflare Access or Azure App Proxy) and kerberos. Initially I wanted to go with NTLM authentication, because you can easily configure it from each of the clients as gateway credentials, however I have hit two issues - Cloudflare doesn’t support proxying NTLM authentication (and I want it published through Cloudflare Tunnel) and NGINX doesn’t support NTLM either (only as part of commercial subscription, there is a free and OSS module but I haven’t tried it, since I want it behind Cloudflare anyways) - I use NGINX as an ingress proxy for all containers and services I host. The next choice was the basic authentication option - and while this would be pretty much suitable for me, it doesn’t work with native MSTSC client (documentation says that RDCMan works, but I didn’t manage to get it working either - RDCMan is just a GUI on top of MSTSC anyways), despites even explicitly setting gatewaycredentialssource to 3. Next, I configured it for header authentication, which is however crashing due to a little bug.
The last option was to go with OpenID Connect setup. This method worked across all platforms, and let me go a little bit into how it works: First, you have to visit https://rdgateway.domain.com/connect?host=xxx.corp.domain.com:3389 and authenticate, which in return will give you a .rdp file preauthenticated to the gateway. You then open the file in MSTSC or Windows (on MacOS or iOS) apps, and authenticate to the target computer. The authentication token to the gateway is shortlived, so in case you need to reconnect, you have to download the file again.
The authentication token is stored in gatewayaccesstoken property in the .rdp file itself (it is a shortlived - 5 minutes - JWT token), which the gateway verifies and let’s you connect. It is also refered to as the PAA Cookie. So thanks to this, you can easily connect to your machine from native client, from anywhere without the need for a VPN or running cloudflared.
The only disappointing thing is, that there’s no interactive way to continuously obtain the gatewayaccesstoken from an IdP in some modern way (so you could skip the entire download a RDP file thing).
I am currently looking into ways to either contribute to the RDGW in Go to fix some of the quirks with the header authentication or leveraging the C# implementation to make my own gateway server in the future.
I also have eyes on Azure Virtual Desktop hybrid deployment. It appears to be in private preview (and the form is already closed). It could remove the need for hosting the gateway completely, since the Arc-enabled machine could be acessibly from AVD directly. I hope to learn more about it soon.
To submit comments, go to GitHub Discussions.