Files
rdpgw/UPGRADING.md
bolkedebruin 13323f56cb Honor X-Forwarded-For only from a trusted-proxy CIDR (#189)
EnrichContext used to copy the first X-Forwarded-For entry into the
request identity unconditionally. The resulting AttrClientIp drives
client-IP comparisons later in the gateway-access flow, and a direct
caller could set XFF to anything they liked.

Add a small package-level allow-list:

* InitTrustedProxies(cidrs) parses operator-supplied CIDRs once at
  startup. A bad CIDR is fatal, an empty list disables XFF entirely.
* EnrichContext takes the client IP from r.RemoteAddr (host portion)
  and only swaps in the first X-Forwarded-For entry when r.RemoteAddr
  itself sits in a trusted-proxy CIDR. AttrProxies is set from the
  remaining XFF entries on the same condition.

Wire Server.TrustedProxies through configuration.go to web.
2026-04-30 18:47:46 +02:00

2.5 KiB

Upgrading

Unreleased

X-Forwarded-For is no longer trusted by default

Previously rdpgw read the first X-Forwarded-For entry into the request identity unconditionally. The resulting client IP attribute is later compared against the value embedded in the gateway access cookie, so any caller reaching rdpgw directly could set X-Forwarded-For to any value and steer that binding.

After upgrading, X-Forwarded-For is honored only when the request arrives from a Server.TrustedProxies CIDR. Otherwise the client IP comes from r.RemoteAddr. The default Server.TrustedProxies is empty, so by default X-Forwarded-For is ignored entirely.

If your deployment fronts rdpgw with a reverse proxy or load balancer on a known subnet, list it:

Server:
  TrustedProxies:
    - 10.0.0.0/8        # the proxy's egress subnet

If no proxy fronts rdpgw, leave TrustedProxies empty -- the request's RemoteAddr is the right source for client identity in that case.

hostselection: any now refuses non-routable destinations and non-RDP ports by default

Previously, when server.hostselection: any was set, rdpgw forwarded to whatever ?host= value the request carried with no check on the target. The gateway would happily relay TCP traffic to loopback, RFC1918, link-local, or arbitrary high-numbered ports on public hosts.

After upgrading, any mode rejects any destination that resolves to a loopback / RFC1918 / link-local / IPv6 ULA / unspecified / multicast address, and any port that is not in AllowedDestinationPorts. The default port allow-list is [3389].

If your deployment legitimately reaches private destinations or extra ports through any mode, opt back in:

Server:
  HostSelection: any
  AllowedDestinationPorts:
    - 3389
    - 5985        # add what you actually need
  AllowPrivateDestinations: true

The other host-selection modes (roundrobin, signed, unsigned) already use the operator-curated Server.Hosts allow-list and are unaffected by this change.

Upgrading from 1.X to 2.0

In 2.0 the options for configuring client side RDP settings have been removed in favor of template file. The template file is a RDP file that is used as a template for the connection. The template file is parsed and a few settings are replaced to ensure the client can connect to the server and the correct domain is used.

The format of the template file is as follows:

# <setting>:<type i or s>:<value>
domain:s:testdomain
connection type:i:2

The filename is set under client > defaults.