Remote Desktop Gateway in Go for deploying on Linux/BSD/Kubernetes
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
Ben Westover b11eb0879e
Clarify build dependencies (#64)
2 months ago
.github/workflows Install pam-devel in code analysis env 7 months ago
cmd Make session length configurable 5 months ago
dev Upgrade Keycloak dependency (#60) 4 months ago
proto Add server implementation of basic auth 7 months ago
shared/auth Add protobuf generated files 7 months ago
.gitignore Bump go to 1.17 and upgrade deps (#32) 9 months ago
LICENSE Add license file 3 years ago
Makefile Update Makefile 7 months ago Clarify build dependencies (#64) 2 months ago
go.mod Refactor identity and http routing 5 months ago

GO Remote Desktop Gateway

Go Docker Pulls Docker Stars Docker Image Size

Star us on GitHub — it helps!

RDPGW is an implementation of the Remote Desktop Gateway protocol. This allows you to connect with the official Microsoft clients to remote desktops over HTTPS. These desktops could be, for example, XRDP desktops running in containers on Kubernetes.


RDPGW aims to provide a full open source replacement for MS Remote Desktop Gateway, including access policies.

Multi Factor Authentication (MFA)

RDPGW provides multi factor authentication out of the box with OpenID Connect integration. Thus you can integrate your remote desktops with Keycloak, Okta, Google, Azure, Apple or Facebook if you want.


NOTE: rdogw now supports PAM authentication as well if you configure it to use 'local' authentication. Further documentation pending.

RDPGW wants to be secure when you set it up from the beginning. It does this by having OpenID Connect integration enabled by default. Cookies are encrypted and signed on the client side relying on Gorilla Sessions. PAA tokens (gateway access tokens) are generated and signed according to the JWT spec by using jwt-go signed with a 256 bit HMAC. Hosts provided by the user are verified against what was provided by the server. Finally, the client's ip address needs to match the one it obtained the token with.

How to build & install

NOTE: a docker image is available on docker hub, which removes the need for building and installing go.

Ensure that you have make (comes with standard build tools, like build-essential on Debian), go (version 1.19 or above), and development files for PAM (libpam0g-dev on Debian) installed.

Then clone the repo and issues the following.

cd rdpgw
make install


By default the configuration is read from rdpgw.yaml. Below is a template.

# web server configuration. 
 # can be set to openid, kerberos and local. If openid is used rdpgw expects
 # a configured openid provider, make sure to set caps.tokenauth to true. If local
 # rdpgw connects to rdpgw-auth over a socket to verify users and password. Note:
 # rdpgw-auth needs to be run as root or setuid in order to work. If kerberos is
 # used a keytab and krb5conf need to be supplied. local and kerberos authentication
 # can be stacked, so that the clients selects what it wants.
  - openid
 # The socket to connect to if using local auth. Ensure rdpgw auth is configured to
 # use the same socket.
 AuthSocket: /tmp/rdpgw-auth.sock
 # The default option 'auto' uses a certificate file if provided and found otherwise
 # it uses letsencrypt to obtain a certificate, the latter requires that the host is reachable
 # from letsencrypt servers. If TLS termination happens somewhere else (e.g. a load balancer)
 # set this option to 'disable'. This is mutually exclusive with 'authentication: local'
 # Note: rdp connections over a gateway require TLS
 Tls: auto
 # TLS certificate files
 CertFile: server.pem
 KeyFile: key.pem
 # gateway address advertised in the rdp files and browser
 GatewayAddress: localhost
 # port to listen on (change to 80 or equivalent if not using TLS)
 Port: 443
 # list of acceptable desktop hosts to connect to
  - localhost:3389
  - my-{{ preferred_username }}-host:3389
 # if true the server randomly selects a host to connect to
 # valid options are: 
 #  - roundrobin, which selects a random host from the list (default)
 #  - signed, a listed host specified in the signed query parameter
 #  - unsigned, a listed host specified in the query parameter
 #  - any, insecurely allow any host specified in the query parameter
 HostSelection: roundrobin 
 # a random strings of at least 32 characters to secure cookies on the client
 # make sure to share this across the different pods
 SessionKey: thisisasessionkeyreplacethisjetzt
 SessionEncryptionKey: thisisasessionkeyreplacethisnunu!
  # where to store session details. This can be either file or cookie (default: cookie)
  # if a file store is chosen, it is required to have clients 'keep state' to the rdpgw
  # instance they are connected to.
 SessionStore: cookie
  # tries to set the receive / send buffer of the connections to the client
 # in case of high latency high bandwidth the defaults set by the OS might
 # be to low for a good experience
 # ReceiveBuf: 12582912
 # SendBuf: 12582912 
# Open ID Connect specific settings
 ProviderUrl: http://keycloak/auth/realms/test
 ClientId: rdpgw
 ClientSecret: your-secret
 Keytab: /etc/keytabs/rdpgw.keytab
 Krb5conf: /etc/krb5.conf
# enabled / disabled capabilities
 SmartCardAuth: false
 TokenAuth: true
 # connection timeout in minutes, 0 is limitless
 IdleTimeout: 10
 EnablePrinter: true
 EnablePort: true
 EnablePnp: true
 EnableDrive: true
 EnableClipboard: true
  # this is a go string templated with {{ username }} and {{ token }}
  # the example below uses the ASCII field separator to distinguish
  # between user and token 
  UsernameTemplate: "{{ username }}\x1f{{ token }}"
  # rdp file settings see: 
  NetworkAutoDetect: 0
  BandwidthAutoDetect: 1
  ConnectionType: 6
  # If true puts splits "" into the user and domain component so that
  # domain gets set in the rdp file and the domain name is stripped from the username
  SplitUserDomain: false
  # a random string of 32 characters to secure cookies on the client
  # make sure to share this amongst different pods
  PAATokenSigningKey: thisisasessionkeyreplacethisjetzt
  # PAATokenEncryptionKey: thisisasessionkeyreplacethisjetzt
  # a random string of 32 characters to secure cookies on the client
  UserTokenEncryptionKey: thisisasessionkeyreplacethisjetzt
  # if you want to enable token generation for the user
  # if true the username will be set to a jwt with the username embedded into it
  EnableUserToken: true
  # Verifies if the ip used to connect to download the rdp file equals from where the
  # connection is opened.
  VerifyClientIp: true

Testing locally

A convenience docker-compose allows you to test the RDPGW locally. It uses Keycloak and xrdp and exposes it services on port 443. You will need to allow your browser to connect to localhost with and self signed security certificate. For chrome set chrome://flags/#allow-insecure-localhost. The username to login to both Keycloak and xrdp is admin as is the password.

cd dev/docker
docker-compose build
docker-compose up


Point your browser to https://your-gateway/connect. After authentication and RDP file will download to your desktop. This file can be opened by one of the remote desktop clients and it will try to connect to the gateway and desktop host behind it.


The gateway exposes an endpoint for the verification of user tokens at https://yourserver/tokeninfo . The query parameter is 'access_token' so you can just do a GET to https://yourserver/tokeninfo?access_token= . It will return 200 OK with the decrypted token.

In this way you can integrate, for example, it with pam-jwt.


  • Integrate Open Policy Agent
  • Integrate GOKRB5
  • Integrate uber-go/zap
  • Research: TLS defragmentation
  • Improve Web Interface