rdpgw

bolkedebruinApache-2.0活跃维护

用 Go 实现的远程桌面网关,可在 Linux、BSD 或 Kubernetes 上部署,为 RDP 连接提供统一入口和转发。

Remote Desktop Gateway in Go for deploying on Linux/BSD/Kubernetes

v3.0.0发布于 2026-11-06 14:08 UTC1,142star +3 / 7 天182fork同步于 2026-11-10 23:51 UTC

怎么上手

由 README 提炼,执行前请确认
docker pull bolkedebruin/rdpgw
硬件要求按 README 说明

README 未说明 CPU、内存或 GPU 要求;可部署在 Linux、BSD、Kubernetes 上,也提供 Docker 镜像。

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 Go 实现的远程桌面网关,让微软官方 RDP 客户端通过 HTTPS 连接内网桌面

用 Go 实现的远程桌面网关,可在 Linux、BSD 或 Kubernetes 上部署,为 RDP 连接提供统一入口和转发。

核心能力

  • 实现 MS-TSGU 远程桌面网关协议,兼容微软官方客户端
  • 支持 OpenID Connect、Kerberos、PAM、NTLM 及代理头认证
  • 内置 OpenID Connect 多因素认证,可对接 Keycloak、Okta 等
  • 按配置白名单校验目标主机,支持轮询、签名、任意三种选择模式
  • 可自动从 Let's Encrypt 申请 TLS 证书或使用自有证书

容易踩的坑

客户端要求 TLS 证书由受信任 CA 签发且 CN 与网关 DNS 主机名匹配,否则 Windows 客户端会拒绝连接;使用 local/PAM 认证时需额外运行 rdpgw-auth(需 root 或 setuid)并通过同一 socket 通信;使用 Kerberos 需提供 keytab 和 krb5.conf,且网关要能连到 KDC;默认读取当前目录的 rdpgw.yaml,默认监听 443 端口。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v3.0.0
  1. 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 get 401. (#184)
    • X-Forwarded-For is ignored by default. It is only used when the request comes from a Server.TrustedProxies CIDR. (#189)
    • hostselection: any refuses private destinations and ports other than 3389. Opt back in with Server.AllowPrivateDestinations and Server.AllowedDestinationPorts. (#188)
    • The rdpgw-auth socket only accepts its own UID. Use --allow-uid or --allow-gid when 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 Authorization headers 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.UsernameClaim selects the ID token claim for the username. Set it to email for Google; the email must be verified. (#197)
    • Client.RdpOverridableKeys lets /connect accept selected RDP settings from the URL, such as ?usemultimon=1. (#182)
    • Server.BindAddress sets the address to listen on. (#196)
    • When a WebSocket upgrade fails, RDG_OUT falls back to the legacy transport. This helps some Android clients and reverse proxies. (#172)

    Fixes

    • rdpgw --help exits 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.sum is committed, and CI fails when go.mod or go.sum is out of date.
    • Dependency updates, including golang.org/x/crypto, golang.org/x/net, google.golang.org/grpc, go-jose and gorilla/websocket.

    Contributors

    Thanks to @Darkar25, @SoundGoof, @vampywiz17, @gorynychzmey and @Fldicoahkiin.

    Full changelog: https://github.com/bolkedebruin/rdpgw/compare/v2.2.0...v3.0.0

  2. v2.2.0Eager Eland2025-09-18 · 2025-09-18 20:45 UTC
    查看更新说明

    Add header authentication

  3. v2.1.0Dunking Donuts2025-09-05 · 2025-09-05 13:12 UTC
    查看更新说明

    What's Changed

    New Contributors

    Full Changelog: https://github.com/bolkedebruin/rdpgw/compare/v2.0.2...v2.1.0

  4. v2.0.22024-03-30 · 2024-03-30 11:21 UTC
    查看更新说明

    Bug fixes in parsing the rdp file and fixes to the dockerfiles

  5. v2.0.1Re-roll2024-03-18 · 2024-03-18 11:13 UTC

    这个版本没有写更新说明。

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    GO Remote Desktop Gateway

    Go Docker Pulls Docker Stars Docker Image Size

    :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 multimonusemultimon
    audiomodeaudiomode
    redirectclipboardredirectclipboard
    screen mode idscreenmodeid

    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)