客户端要求 TLS 证书由受信任 CA 签发且 CN 与网关 DNS 主机名匹配,否则 Windows 客户端会拒绝连接;使用 local/PAM 认证时需额外运行 rdpgw-auth(需 root 或 setuid)并通过同一 socket 通信;使用 Kerberos 需提供 keytab 和 krb5.conf,且网关要能连到 KDC;默认读取当前目录的 rdpgw.yaml,默认监听 443 端口。
rdpgw
bolkedebruinApache-2.0活跃维护用 Go 实现的远程桌面网关,可在 Linux、BSD 或 Kubernetes 上部署,为 RDP 连接提供统一入口和转发。
Remote Desktop Gateway in Go for deploying on Linux/BSD/Kubernetes
docker pull bolkedebruin/rdpgwREADME 未说明 CPU、内存或 GPU 要求;可部署在 Linux、BSD、Kubernetes 上,也提供 Docker 镜像。
用 Go 实现的远程桌面网关,让微软官方 RDP 客户端通过 HTTPS 连接内网桌面
用 Go 实现的远程桌面网关,可在 Linux、BSD 或 Kubernetes 上部署,为 RDP 连接提供统一入口和转发。
核心能力
- 实现 MS-TSGU 远程桌面网关协议,兼容微软官方客户端
- 支持 OpenID Connect、Kerberos、PAM、NTLM 及代理头认证
- 内置 OpenID Connect 多因素认证,可对接 Keycloak、Okta 等
- 按配置白名单校验目标主机,支持轮询、签名、任意三种选择模式
- 可自动从 Let's Encrypt 申请 TLS 证书或使用自有证书
容易踩的坑
- v3.0.0Funky Fox最新正式版2026-11-06 · 2026-11-06 14:08 UTC
Funky Fox is a security-hardening release. Several defaults are now stricter, so a 2.x configuration may need changes before 3.0 starts. Read UPGRADING.md before you upgrade.
Breaking changes
- Placeholder secrets are refused. rdpgw does not start when a session or token key is one of the example values from the README or the dev compose files. (#191)
- Header authentication requires
Header.TrustedProxies. Without it rdpgw does not start, and requests from outside those CIDRs get401. (#184) X-Forwarded-Foris ignored by default. It is only used when the request comes from aServer.TrustedProxiesCIDR. (#189)hostselection: anyrefuses private destinations and ports other than 3389. Opt back in withServer.AllowPrivateDestinationsandServer.AllowedDestinationPorts. (#188)- The
rdpgw-authsocket only accepts its own UID. Use--allow-uidor--allow-gidwhen rdpgw runs as another user. (#190) - RDP files downloaded before the upgrade stop working. The gateway access token has a new format. (#187)
- OIDC logins without a username claim are rejected instead of continuing with an empty username. (#197)
- The container image runs as UID 1001 and no longer contains a TLS key. Mount your own certificate, or the entrypoint generates a self-signed one at first start. (#191)
- Building from source requires Go 1.26. (#212)
Security
- Cached tunnels can only be reused by the session that opened them. (#185)
- Control characters are removed from strings written to RDP files, so a username cannot add RDP settings. (#183)
- The connection to the RDP host is closed when the channel ends. Before, each connection left a host connection open. (#214)
- Malformed packets, concurrent connection tracking and short
Authorizationheaders no longer crash the gateway. (#186) - The gateway access token is smaller, does not contain the IdP access token, and is bound to its own audience. (#187)
New
- Web interface for choosing a server and downloading its RDP file, with customizable templates (
cmd/rdpgw/templates). OpenId.UsernameClaimselects the ID token claim for the username. Set it toemailfor Google; the email must be verified. (#197)Client.RdpOverridableKeyslets/connectaccept selected RDP settings from the URL, such as?usemultimon=1. (#182)Server.BindAddresssets the address to listen on. (#196)- When a WebSocket upgrade fails,
RDG_OUTfalls back to the legacy transport. This helps some Android clients and reverse proxies. (#172)
Fixes
rdpgw --helpexits cleanly instead of panicking, and invalid flags exit with code 1.- The web interface redirects to login when the session has expired.
Build and packaging
- Container images build with Go 1.27, and from the commit being released. (#212)
go.sumis committed, and CI fails whengo.modorgo.sumis out of date.- Dependency updates, including
golang.org/x/crypto,golang.org/x/net,google.golang.org/grpc,go-joseandgorilla/websocket.
Contributors
Thanks to @Darkar25, @SoundGoof, @vampywiz17, @gorynychzmey and @Fldicoahkiin.
Full changelog: https://github.com/bolkedebruin/rdpgw/compare/v2.2.0...v3.0.0
- v2.2.0Eager Eland2025-09-18 · 2025-09-18 20:45 UTC
查看更新说明
Add header authentication
- v2.1.0Dunking Donuts2025-09-05 · 2025-09-05 13:12 UTC
查看更新说明
What's Changed
- Bump golang.org/x/net from 0.22.0 to 0.23.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/111
- Support for NTLM authentication added by @m7913d in https://github.com/bolkedebruin/rdpgw/pull/109
- Bump golang.org/x/crypto from 0.21.0 to 0.31.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/130
- Updated README.md for adding link to docker hub by @firmanalams in https://github.com/bolkedebruin/rdpgw/pull/128
- Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.4 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/138
- Bump github.com/go-jose/go-jose/v4 from 4.0.1 to 4.0.5 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/136
- Disable UserTokenSigningKey randomization by @beatrubi in https://github.com/bolkedebruin/rdpgw/pull/107
- fix: handle multiple message frames inside packet by @mfmarche in https://github.com/bolkedebruin/rdpgw/pull/143
- Bump golang.org/x/net from 0.23.0 to 0.38.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/142
- Bump github.com/go-viper/mapstructure/v2 from 2.0.0-alpha.1 to 2.3.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/148
- Add tzdata package for showing local time in log by @ppc52776 in https://github.com/bolkedebruin/rdpgw/pull/149
- Allow signing downloaded RDP file by @andrewheberle in https://github.com/bolkedebruin/rdpgw/pull/156
- Bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/155
- Bump golang.org/x/oauth2 from 0.18.0 to 0.27.0 by @dependabot[bot] in https://github.com/bolkedebruin/rdpgw/pull/151
- Ensure autoreconnect setting matches documentation by @andrewheberle in https://github.com/bolkedebruin/rdpgw/pull/157
New Contributors
- @m7913d made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/109
- @firmanalams made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/128
- @beatrubi made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/107
- @mfmarche made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/143
- @ppc52776 made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/149
- @andrewheberle made their first contribution in https://github.com/bolkedebruin/rdpgw/pull/156
Full Changelog: https://github.com/bolkedebruin/rdpgw/compare/v2.0.2...v2.1.0
- v2.0.22024-03-30 · 2024-03-30 11:21 UTC
查看更新说明
Bug fixes in parsing the rdp file and fixes to the dockerfiles
- v2.0.1Re-roll2024-03-18 · 2024-03-18 11:13 UTC
这个版本没有写更新说明。
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
GO Remote Desktop Gateway
:star: 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.
AIM
RDPGW aims to provide a full open source replacement for MS Remote Desktop Gateway, including access policies.
Security requirements
Several security requirements are stipulated by the client that is connecting to it and some are enforced by the gateway. The client requires that the server's TLS certificate is valid and that it is signed by a trusted authority. In addition, the common name in the certificate needs to match the DNS hostname of the gateway. If these requirements are not met the client will refuse to connect.
The gateway has several security phases. In the authentication phase the client's credentials are verified. Depending the authentication mechanism used, the client's credentials are verified against an OpenID Connect provider, Kerberos, a local PAM service, a local database, or extracted from HTTP headers provided by upstream proxy services.
If OpenID Connect is used the user will need to connect to a webpage provided by the gateway to authenticate, which in turn will redirect the user to the OpenID Connect provider. If the authentication is successful the browser will download a RDP file with temporary credentials that allow the user to connect to the gateway by using a remote desktop client.
If Kerberos is used the client will need to have a valid ticket granting ticket (TGT). The gateway will proxy the TGT request to the KDC. Therefore, the gateway needs to be able to connect to the KDC and a krb5.conf file needs to be provided. The proxy works without the need for an RDP file and thus the client can connect directly to the gateway.
If local authentication is used the client will need to provide a username and password that is verified
against PAM. This requires, to ensure privilege separation, that rdpgw-auth is also running and a
valid PAM configuration is provided per typical configuration.
If NTLM authentication is used, the allowed user credentials for the gateway should be configured in the
configuration file of rdpgw-auth.
Finally, RDP hosts that the client wants to connect to are verified against what was provided by / allowed by the server. Next to that the client's ip address needs to match the one it obtained the gateway token with if using OpenID Connect. Due to proxies and NAT this is not always possible and thus can be disabled. However, this is a security risk.
Configuration
The configuration is done through a YAML file. The configuration file is read from rdpgw.yaml by default.
At the bottom of this README is an example configuration file. In these sections you will find the most important
settings.
Authentication
RDPGW wants to be secure when you set it up from the start. It supports several authentication mechanisms such as OpenID Connect, Kerberos, PAM, NTLM, and header-based authentication for proxy integration.
Technically, 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.
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.
Mixing authentication mechanisms
It is technically possible to mix authentication mechanisms. Currently, you can mix local with Kerberos or NTLM. If you enable OpenID Connect it is not possible to mix it with local or Kerberos at the moment.
OpenID Connect
For detailed OpenID Connect setup with providers like Keycloak, Azure AD, Google, and others, see the OpenID Connect Authentication Documentation.
Kerberos
For detailed Kerberos setup including keytab generation, DNS requirements, and KDC proxy configuration, see the Kerberos Authentication Documentation.
PAM/Local Authentication
For detailed PAM setup including LDAP integration, container deployment, and compatible clients, see the PAM Authentication Documentation.
NTLM Authentication
For detailed NTLM setup including user management, security considerations, and deployment options, see the NTLM Authentication Documentation.
Header Authentication (Proxy Integration)
RDPGW supports header-based authentication for integration with reverse proxy services (Azure App Proxy, Google IAP, AWS ALB, etc.) that handle authentication upstream and pass user identity via HTTP headers.
For detailed configuration and examples, see the Header Authentication Documentation.
TLS
The gateway requires a valid TLS certificate. This means a certificate that is signed by a valid CA that is in the store of your clients. If this is not the case particularly Windows clients will fail to connect. You can either provide a certificate and key file or let the gateway obtain a certificate from letsencrypt. If you want to use letsencrypt make sure that the host is reachable on port 80 from the letsencrypt servers.
For letsencrypt:
Tls: auto
for your own certificate:
Tls: enable
CertFile: server.pem
KeyFile: key.pem
NOTE: You can disable TLS on the gateway, but you will then need to make sure a proxy is run in front of it that does TLS termination.
Example configuration file for Open ID Connect
# web server configuration.
Server:
# can be set to openid, kerberos, local and ntlm. 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 can be stacked with
# kerberos or ntlm authentication, so that the clients selects what it wants.
Authentication:
# - kerberos
# - local
- openid
# - ntlm
# The socket to connect to if using local auth. Ensure rdpgw auth is configured to
# use the same socket.
# AuthSocket: /tmp/rdpgw-auth.sock
# Basic auth timeout (in seconds). Useful if you're planning on waiting for MFA
BasicAuthTimeout: 5
# 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
# 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
# local address to bind both the gateway and automatic TLS challenge listeners.
# Empty binds to all interfaces. IPv4 and IPv6 addresses are supported.
# BindAddress: 127.0.0.1
# list of acceptable desktop hosts to connect to
Hosts:
- 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, allow any host specified in the query parameter, gated by the
# AllowedDestinationPorts / AllowPrivateDestinations options below.
HostSelection: roundrobin
# When HostSelection: any, only these TCP ports may be forwarded to.
# Empty defaults to [3389]. Ignored for the curated host modes
# (roundrobin, signed, unsigned).
# AllowedDestinationPorts:
# - 3389
# When HostSelection: any, refuse to forward to loopback / RFC1918 /
# link-local / IPv6 ULA / unspecified / multicast destinations unless
# this is true. Default false. Has no effect on the curated host modes.
# AllowPrivateDestinations: false
# CIDR allow-list of upstream proxies whose X-Forwarded-For header is
# trusted when deriving the client IP. Empty (default) makes the
# gateway ignore X-Forwarded-For and use the request's RemoteAddr.
# Set this to the proxy/load-balancer subnet that fronts the gateway.
# TrustedProxies:
# - 10.0.0.0/8
# 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
OpenId:
ProviderUrl: http://keycloak/auth/realms/test
ClientId: rdpgw
ClientSecret: your-secret
# claim that holds the username; by default the first of preferred_username,
# unique_name, upn and username. email requires email_verified.
# UsernameClaim: email
# Kerberos:
# Keytab: /etc/keytabs/rdpgw.keytab
# Krb5conf: /etc/krb5.conf
# enabled / disabled capabilities
Caps:
SmartCardAuth: false
# required for openid connect
TokenAuth: true
# connection timeout in minutes, 0 is limitless
IdleTimeout: 10
EnablePrinter: true
EnablePort: true
EnablePnp: true
EnableDrive: true
EnableClipboard: true
Client:
# template rdp file to use for clients
# rdp file settings and their defaults see here:
# https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/rdp-files
defaults: /etc/rdpgw/default.rdp
# 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 }}@bla.com\x1f{{ token }}"
# If true puts splits "[email protected]" 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
# If true, removes "username" (and "domain" if SplitUserDomain is true) from RDP file.
# NoUsername: true
# If both SigningCert and SigningKey are set the downloaded RDP file will be signed
# so the client can authenticate the validity of the RDP file and reduce warnings from
# the client if the CA that issued the certificate is trusted. Both should be PEM encoded
# and the key must be an unencrypted RSA private key.
# SigningCert: /path/to/signing.crt
# SigningKey: /path/to/signing.key
Security:
# 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
# Signing makes the token bigger and we are limited to 511 characters
# UserTokenSigningKey: 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
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
make install
Testing locally
A convenience docker-compose allows you to test the RDPGW locally. It uses Keycloak
and xrdp and exposes it services on port 9443. 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.
NOTE: The redirecting relies on DNS. Make sure to add 127.0.0.1 keycloak to your /etc/hosts file to ensure
that the redirect works.
NOTE: The local testing environment uses a self signed certificate. This works for MAC clients, but not for Windows. If you want to test it on Windows you will need to provide a valid certificate.
# with open id
cd dev/docker
docker-compose -f docker-compose.yml up
# or for arm64 with open id
docker-compose -f docker-compose-arm64.yml up
# or for local or pam
docker-compose -f docker-compose-local.yml up
You can then connect to the gateway at https://localhost:9443/connect for the OpenID connect flavors which will start
the authentication flow. Or you can connect directly with the gateway set and the host set to xrdp if using the local
flavor. You can login with 'admin/admin'. The RDP file will download and you can open it with a remote
desktop client. Also for logging in 'admin/admin' will work.
Use
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.
Overriding RDP options from the URL
The /connect endpoint can apply caller-supplied RDP setting overrides from
URL query parameters when (and only when) the operator explicitly opts in.
This lets users vary settings such as multi-monitor, audio mode or
clipboard redirection without maintaining separate RDP templates.
The query parameter name is the underlying RDP key with whitespace removed and lowercased. For example:
| RDP key (in the file) | Query parameter |
|---|---|
use multimon | usemultimon |
audiomode | audiomode |
redirectclipboard | redirectclipboard |
screen mode id | screenmodeid |
Values are validated against the field type: booleans accept 0/1 or
true/false, integers must parse as base-10 numbers, strings are taken
verbatim. Unknown query parameters are ignored so the existing host,
etc. continue to work.
To enable, list the keys you want to expose under client.rdpoverridablekeys
in your YAML configuration (or via the RDPGW_CLIENT__RDPOVERRIDABLEKEYS
environment variable as a space-separated list):
client:
rdpoverridablekeys:
- use multimon
- audiomode
With the snippet above, https://your-gateway/connect?usemultimon=1 adds
use multimon:i:1 to the generated RDP file. Keys that are not on the
allow-list are rejected with 400 Bad Request. Authoritative settings
controlled by the gateway (gateway address, access token, full address,
username) always win over URL parameters even if an operator adds those
keys to the allow-list — but adding sensitive keys is still discouraged.
Integration
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.
Client Caveats
The several clients that Microsoft provides come with their own caveats.
The most important one is that the default client on Windows mstsc does
not support basic authentication. This means you need to use either OpenID Connect,
Kerberos or ntlm authentication.
In addition to that, mstsc, when configuring a gateway directly in the client requires
you to either:
- "save the credentials" for the gateway
- or specify a (random) domain name in the username field (e.g.
.\username) when prompted for the gateway credentials,
otherwise the client will not connect at all (it won't send any packages to the gateway) and it will keep on asking for new credentials.
Finally, mstsc requires a valid certificate on the gateway.
Additionally, mstsc is more restrictive about SSL cipher suites compared to other RDP clients. When using a reverse proxy like nginx for TLS termination, you may need to configure specific cipher suites that mstsc supports. A working configuration for nginx ssl_ciphers is:
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256
mstsc also requires server names rather than IP addresses for connections, despite Microsoft's documentation suggesting otherwise. When configuring hosts in the rdpgw configuration, ensure you use hostnames.
Furthermore, the mstsc client sends the hostname including the port number when establishing connections. To ensure proper host verification, configure your hosts in the rdpgw configuration file with the port numbers included (e.g., myserver:3389 even for the default RDP port 3389).
The Microsoft Remote Desktop Client from the Microsoft Store does not have these issues, but it requires that the username and password used for authentication are the same for both the gateway and the RDP host.
The Microsoft Remote Desktop Client for Mac does not have these issues and is the most flexible. It supports basic authentication, OpenID Connect and Kerberos and can use different credentials
The official Microsoft IOS and Android clients seem also more flexible.
Third party clients like FreeRDP might also provide more flexibility.
TODO
- Improve Web Interface
Acknowledgements
- This product includes software developed by the Thomson Reuters Global Resources. (go-ntlm - BSD-4 License)