Files
rdpgw/docs/header-authentication.md
Bolke de Bruin ddc037ea41 Require trusted-proxy CIDR allow-list for header authentication
Header auth previously trusted any request that carried the configured
user header, with no check that the request came from a known upstream
proxy. Anyone reaching rdpgw directly could mint an authenticated
session as any user by setting the header.

Add `Header.TrustedProxies` (CIDR list) checked against `RemoteAddr`
before reading the user header. Refuse the request with 401 when the
remote is outside the allow-list. Refuse to start when header
authentication is enabled but `Header.TrustedProxies` is empty.

The CIDR allow-list gates the immediate upstream only; operators must
still configure their proxy to strip duplicate inbound copies of the
user header so a client cannot smuggle one through the trusted hop.
Documented in docs/header-authentication.md.

TestHeaderAuthRequiresTrustedProxy is a 3-case table covering: no
allow-list (refused), outside allow-list (refused), inside allow-list
(allowed). Existing TestHeaderAuthenticated cases updated to declare
trust for httptest.NewRequest's default RemoteAddr (192.0.2.1).
2026-04-30 13:42:36 +02:00

7.7 KiB

Header Authentication

RDPGW supports header-based authentication for integration with reverse proxy services that handle authentication upstream.

Configuration

Server:
  Authentication:
    - header
  Tls: disable  # Proxy handles TLS termination

Header:
  UserHeader: "X-Forwarded-User"        # Required: Username header
  UserIdHeader: "X-Forwarded-User-Id"   # Optional: User ID header
  EmailHeader: "X-Forwarded-Email"      # Optional: Email header
  DisplayNameHeader: "X-Forwarded-Name" # Optional: Display name header
  # Required: CIDR allow-list of upstream proxies that may stamp the
  # headers above. Requests arriving from any other RemoteAddr are
  # refused with 401, so the user header cannot be minted by callers
  # that bypass the proxy. RDPGW refuses to start when this is empty.
  TrustedProxies:
    - "10.0.0.0/8"

Caps:
  TokenAuth: true

Security:
  VerifyClientIp: false  # Requests come through proxy

Proxy Service Examples

Microsoft Azure Application Proxy

Server:
  Authentication:
    - header
  Tls: disable  # App Proxy handles TLS termination

Header:
  UserHeader: "X-MS-CLIENT-PRINCIPAL-NAME"
  UserIdHeader: "X-MS-CLIENT-PRINCIPAL-ID"
  EmailHeader: "X-MS-CLIENT-PRINCIPAL-EMAIL"
  TrustedProxies:
    # Reach RDPGW only via Azure Private Link / a private VNet peering, then
    # list the connector subnet here. Do not expose RDPGW publicly when using
    # App Proxy: the App Proxy egress is not a fixed IP range, so a public
    # listener cannot be safely gated by CIDR.
    - "10.0.0.0/8"

Security:
  VerifyClientIp: false  # Required for App Proxy

Caps:
  TokenAuth: true  # Essential for RDP client connections

Azure Configuration:

  1. Create App Registration in Azure AD:

    # Note the Application ID for App Proxy configuration
    az ad app create --display-name "RDPGW-AppProxy"
    
  2. Configure Application Proxy:

    • Internal URL: http://rdpgw-internal:80 (or your internal RDPGW address)
    • External URL: https://rdpgw.yourdomain.com
    • Pre-authentication: Azure Active Directory
    • Pass through: Enabled for /remoteDesktopGateway/
  3. Configure Conditional Access Policies:

    • Target the RDPGW App Proxy application
    • Set device compliance, location restrictions, MFA requirements
    • Enable session controls as needed

Important App Proxy Configuration:

{
  "name": "RDPGW",
  "internalUrl": "http://rdpgw-internal",
  "externalUrl": "https://rdpgw.yourdomain.com",
  "preAuthenticatedApplication": {
    "preAuthenticationType": "AzureActiveDirectory",
    "passthroughPaths": [
      "/remoteDesktopGateway/*"
    ]
  }
}

Authentication Flow:

  1. Web Authentication (/connect endpoint):

    User Browser → App Proxy (Azure AD auth) → RDPGW → Downloads RDP file
    
  2. RDP Client Connection (/remoteDesktopGateway/ endpoint):

    RDP Client → App Proxy (passthrough) → RDPGW (token validation) → RDP Host
    

Key Requirements:

  • Passthrough configuration for /remoteDesktopGateway/ path
  • Header authentication only for /connect endpoint
  • Token-based auth for actual RDP connections
  • Disable IP verification due to App Proxy NAT

Google Cloud Identity-Aware Proxy (IAP)

Header:
  UserHeader: "X-Goog-Authenticated-User-Email"
  UserIdHeader: "X-Goog-Authenticated-User-ID"
  EmailHeader: "X-Goog-Authenticated-User-Email"
  TrustedProxies:
    - "35.191.0.0/16"      # Google IAP / load balancer health checkers
    - "130.211.0.0/22"     # Google Cloud Load Balancing

Setup: Enable IAP on your Cloud Load Balancer pointing to RDPGW. Configure OAuth consent screen and authorized users/groups.

AWS Application Load Balancer (ALB) with Cognito

Header:
  UserHeader: "X-Amzn-Oidc-Subject"
  EmailHeader: "X-Amzn-Oidc-Email"
  DisplayNameHeader: "X-Amzn-Oidc-Name"
  TrustedProxies:
    # Place RDPGW in a private subnet whose only ingress is the ALB, then
    # list the ALB-facing subnet here.
    - "10.0.0.0/16"

Setup: Configure ALB with Cognito User Pool authentication. Enable OIDC headers forwarding to RDPGW target group.

Traefik with ForwardAuth

Header:
  UserHeader: "X-Forwarded-User"
  EmailHeader: "X-Forwarded-Email"
  DisplayNameHeader: "X-Forwarded-Name"
  TrustedProxies:
    - "172.16.0.0/12"  # Docker network or whatever subnet Traefik runs on

Setup: Use Traefik ForwardAuth middleware with external auth service (e.g., OAuth2 Proxy, Authelia) that sets headers.

nginx with auth_request

Header:
  UserHeader: "X-Auth-User"
  EmailHeader: "X-Auth-Email"
  TrustedProxies:
    - "127.0.0.0/8"  # nginx on the same host as RDPGW

nginx config:

upstream rdpgw {
    server rdpgw:443;
}

upstream auth-service {
    server auth-service:80;
}

server {
    listen 443 ssl http2;
    server_name your-gateway.example.com;

    # SSL configuration
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Auth endpoint (internal)
    location /auth {
        internal;
        proxy_pass http://auth-service;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Original-Method $request_method;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Main location with auth and WebSocket support
    location / {
        # Authentication
        auth_request /auth;
        auth_request_set $user $upstream_http_x_auth_user;
        auth_request_set $email $upstream_http_x_auth_email;

        # Forward user headers to RDPGW
        proxy_set_header X-Auth-User $user;
        proxy_set_header X-Auth-Email $email;

        # WebSocket and HTTP upgrade support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouts for long-lived connections
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;

        # Disable buffering for real-time protocols
        proxy_buffering off;

        proxy_pass https://rdpgw;
    }
}

# WebSocket upgrade mapping
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

Security Considerations

  • TrustedProxies is mandatory: RDPGW refuses to start when header authentication is enabled but Header.TrustedProxies is empty. Any request with a RemoteAddr outside the configured CIDRs is rejected with 401 before the user header is read. Without this gate any caller on the network could mint an authenticated session by setting the header.
  • Header Validation: Even with a CIDR allow-list, configure the proxy to strip duplicate inbound copies of UserHeader (and the optional id/email/display-name headers) so a client cannot smuggle one through the trusted proxy.
  • Network Security: Deploy RDPGW in a private network accessible only via the proxy. The CIDR allow-list is a second line of defense, not a replacement for network segmentation.
  • TLS: Enable TLS between proxy and RDPGW in production environments.

Validation

Test header authentication via your proxy (the request must reach RDPGW from a TrustedProxies CIDR):

curl -H "X-Forwarded-User: testuser@domain.com" \
     https://your-proxy/connect

A direct request to RDPGW from outside the trusted-proxy range must return 401 Unauthorized even when the user header is set:

curl -H "X-Forwarded-User: testuser@domain.com" \
     https://rdpgw-internal/connect
# HTTP/1.1 401 Unauthorized
# Untrusted upstream