Commit Graph

106 Commits

Author SHA1 Message Date
bolkedebruin
49fa170023 Make the PAA cookie self-contained and shrink it (#187)
CheckPAACookie used to call OIDCProvider.UserInfo with a copy of the
IdP access token embedded in the cookie itself, just to recover the
Subject. The gateway already signs Subject at issue time, so the
roundtrip is redundant -- and copying the IdP access token into the
.rdp file makes the cookie much larger and ties gateway availability
to IdP availability.

Drop the AccessToken field from customClaims and from GeneratePAAToken
(no other consumer exists), set tunnel.User.SetUserName from the
signed Subject claim, and remove the UserInfo call from
CheckPAACookie.

Add Audience: "rdpgw-paa" to standard claims at issue and
AnyAudience to the validation expectation so a JWS minted with the
same signing key for any other purpose can't be presented as a PAA.

For a representative RS256 access token the cookie shrinks from
~961 bytes to ~259 bytes.

Adds tests:
- TestPAACookieDoesNotEmbedAccessToken
- TestPAACookieHasAudienceClaim
- TestCheckPAACookieIsSelfContained
2026-04-30 18:22:22 +02:00
bolkedebruin
8fc5677dfc Harden three small panic / race paths (#186)
Three independent corrections to functions that crash the worker on
malformed or concurrent input:

* protocol/common.readHeader: validate the on-wire size field before
  slicing data[8:size]. Introduce a named headerLen constant and reject
  declared sizes outside [headerLen, headerLen+maxFragmentSize], so a
  size of 0..7 (which previously panicked with slice bounds out of
  range) and an oversized size both surface as an error instead.

* protocol/track: serialize access to the global Connections map with
  a sync.RWMutex. Concurrent RegisterTunnel/RemoveTunnel calls would
  otherwise be caught by the runtime as a fatal `concurrent map
  writes`. Also correct the inverted condition in Disconnect (the
  previous code dereferenced a nil Monitor when the id was missing and
  returned "does not exist" when the id was present).

* web/ntlm.getAuthPayload: switch from authorisationEncoded[0:5] /
  [0:10] to strings.HasPrefix so an Authorization header shorter than
  the prefixes returns an error instead of a slice-bounds panic.

Adds:
- TestReadHeaderRejectsUndersizedSize (sizes 0/1/2/7).
- TestTunnelTrackerConcurrent (200 goroutine pairs).
- TestDisconnectKnownConnection / TestDisconnectMissingConnectionDoesNotPanic.
- TestGetAuthPayloadShortHeader (missing/empty/3/4/5/9 character values).
2026-04-30 14:38:36 +02:00
bolkedebruin
628046b34c Bind cached tunnel reuse to the original session identity (#185)
HandleGatewayProtocol caches a *Tunnel keyed on the client-supplied
Rdg-Connection-Id header so the two halves of a session (RDG_OUT_DATA
and RDG_IN_DATA) can rendezvous on a single record. The cache hit path
previously reused the tunnel without checking who was making the
follow-up request.

Add tunnelOwnerMatches(t, id) to compare the cached tunnel's
UserName() and AttrClientIp against the request identity. On
mismatch, refuse with 401 instead of attaching the new request to the
existing tunnel. The helper is conservative: nil tunnel/user/identity,
empty username, or missing client-IP attribute all fail closed.

The legitimate case (the same client returns to attach its second
half-channel to its own first half) is unchanged.

Adds TestTunnelOwnershipEnforced.
2026-04-30 14:07:50 +02:00
bolkedebruin
75ef8ce289 Require trusted-proxy CIDR allow-list for header authentication (#184)
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:47:01 +02:00
bolkedebruin
cbb1e5feb3 Strip ASCII control bytes from rendered RDP string fields (#183)
The .rdp file format is line-delimited (key:type:value\r\n), and an
unfiltered \r, \n, or NUL inside a string field is reinterpreted by RDP
clients as a directive boundary. A username flowing in from any of OIDC,
header auth, NTLM, or the URL-override path could therefore inject
arbitrary additional directives — e.g. `alternate shell:s:cmd.exe` — and
when RDP signing is enabled the malicious payload is signed as
authentic, producing a one-click client-side RCE on every user who
opens the file.

Strip bytes < 0x20 and 0x7F at the renderer chokepoint
(addStructToString), so every source path — caller, template file,
ApplyOverrides, anything future — passes through the same filter.
Legitimate values (usernames, base64url tokens, hostnames) contain no
such bytes, so the filter is a no-op for normal input. Stripping is
logged so operators can spot rejected input.

Adds TestStringFieldBoundaryHygiene covering CRLF in username, domain
and full address; bare LF in alternate shell; and embedded NUL.

Co-authored-by: Bolke de Bruin <bolke.debruin@metyis.com>
2026-04-30 13:33:35 +02:00
bolkedebruin
754896b473 Add type-safe URL override mechanism for RDP options (#182)
Introduce a generic, allow-list-gated way for the /connect endpoint to
accept RDP setting overrides via URL query parameters. Operators opt in
via client.rdpoverridablekeys; absent that allow-list, URL-driven
overrides are rejected with 400.

Override values are routed through rdp.Builder.ApplyOverrides, which
matches query keys against the rdp struct tags of RdpSettings and
validates per Go field type. Overridden fields are tracked so explicit
values always serialize even when they equal the field default. The
override pass runs before authoritative server-controlled fields
(gateway address, access token, full address, username) so those
always win.

This replaces the per-option string-splice approach considered in #181:
multimon now works via ?usemultimon=1 against an operator allow-list
containing "use multimon", and any other RDP key follows the same path
without bespoke handler code.
2026-04-30 11:58:47 +02:00
Darkar25
4d58a9eb97 Fix protocol fallback (#172)
* Fix protocol fallback

* Use token-aware header matching, drop dead fallback log

Connection is a list header; plain equality misses legitimate
"keep-alive, Upgrade" clients. Switch to case-insensitive token
matching for the Connection/Upgrade checks.

Remove the "falling back to old protocol" log on upgrade failure.
upgrader.Upgrade commits an HTTP error response before returning, so
the follow-up legacy path cannot produce a coherent reply. The real
fallback happens at the header pre-check for clients and reverse
proxies that strip the upgrade tokens.

Add tests for the header helper and RDGOUT routing.
2026-04-23 19:35:16 +02:00
Bolke de Bruin
1d35d6ede0 detect html as not being authenticated anymore 2025-09-26 19:18:47 +02:00
Bolke de Bruin
a4fc955fe4 Redirect when authentication expires 2025-09-26 00:25:44 +02:00
Bolke de Bruin
debcc81384 Add webinterface 2025-09-25 15:34:05 +02:00
Bolke de Bruin
21a88d2dea Add webinterface 2025-09-25 15:33:46 +02:00
Bolke de Bruin
86c277cea4 Use session store for state 2025-09-23 21:14:40 +02:00
Bolke de Bruin
75a7ca62a9 Add header authentication 2025-09-18 22:36:04 +02:00
Andrew Heberle
fee421beba fix: ensure autoreconnect setting matches documentation (#157)
Co-authored-by: bolkedebruin <bolkedebruin@users.noreply.github.com>
2025-09-05 15:10:48 +02:00
Bolke de Bruin
85fec5fb2a fix: improve ios compatibility 2025-09-05 15:02:57 +02:00
Bolke de Bruin
232f70f155 fix: linting issue 2025-09-05 14:31:15 +02:00
Andrew Heberle
2b9ec4a3f0 Allow signing downloaded RDP file (#156)
Implement signing of RDP files downloaded from web
2025-09-05 14:21:32 +02:00
Mike Marchetti
20307b9a76 fix: handle multiple message frames inside packet (#143)
Running the gateway as non-tls, but using an external TLS gateway in
kubernetes+istio, I determined that the istio TLS gateway would join
messages frames into a single TCP packet. The packet read code assumed
that a single packet is a message. This is not the case for a TCP
stream, since you don't know how the frames are segmented via proxies,
etc.

The fix turned out more complex that I would have liked, but added a
number of unit tests to cover all the corner cases. Likely fragmentation
was not working correctly as well, as there was some cases that were
previously not handled.

Note that this might address issue #126 as well.
2025-05-06 17:38:16 +02:00
Beat Rubischon
6b4e6bdced Disable UserTokenSigningKey randomization (#107) 2025-02-27 15:06:29 +01:00
m7913d
372dc43ef2 Support for NTLM authentication added (#109)
* Support for NTLM authentication added

To support NTLM authentication, a database is added as an authentication source.
Currently, only the configuration file is supported as a database.
Database authentication supports Basic and NTLM authentication protcols.

ServerConfig.BasicAuthEnabled renamed to LocalEnabled as Basic auth can be used with NTLM or Local.
2024-04-24 14:12:41 +02:00
Bolke de Bruin
d76ccf324a Let's not leak 2024-04-12 12:44:07 +02:00
Bolke de Bruin
9c6d056d69 Use jose v4 and make clearer and fix signing/encryption 2024-04-12 12:33:46 +02:00
Bolke de Bruin
bc36b2b0cb Fix b parsing 2024-03-30 12:12:55 +01:00
Bolke de Bruin
a963ca0d00 Fix parsing of bool to int 2024-03-30 12:07:28 +01:00
Bolke de Bruin
5d30deb48c Add untested explicit settings in rdp file 2024-03-21 16:22:14 +01:00
Bolke de Bruin
95a8623cb6 Change remoteapplicationmode to default to false as that seems to be the case 2024-03-21 15:35:45 +01:00
Bolke de Bruin
447599b92a Add request uri for better debugging 2024-03-20 10:56:58 +01:00
Bolke de Bruin
a7ea3121d9 Only split when required 2024-03-19 10:23:57 +01:00
Bolke de Bruin
7bf2a59838 Testing 2024-03-19 10:20:14 +01:00
Bolke de Bruin
ec63346c8a Handle arrays in env variables 2024-03-19 09:42:19 +01:00
Bolke de Bruin
46620c87b7 upgrades 2024-03-18 15:27:30 +01:00
Bolke de Bruin
e939275a8a Make dynamic 2024-03-18 14:09:22 +01:00
Bolke de Bruin
1b1d54b572 Debug 2024-03-18 14:03:18 +01:00
Bolke de Bruin
91e382c586 Move to more flexibility in image 2024-03-18 13:36:41 +01:00
Bolke de Bruin
ecbe63f175 Use list of kdcs and ensure length is removed / added when necessary 2024-03-16 13:10:30 +01:00
Bolke de Bruin
a67962b02d Fix no username issues 2024-03-16 11:32:02 +01:00
Jonathan Giroux (Koltes)
8e117ad083 Can omit username from rendered RDP (#83) 2024-03-15 12:30:22 +01:00
fliaping
6325c0c4b7 add "username" as claim key (#98) 2024-03-15 12:29:00 +01:00
ryanblenis
f72613c2ba Add BasicAuthTimeout setting versus static 5 seconds (#90) 2023-12-16 21:07:37 +01:00
Bolke de Bruin
e9e592b43a Add missing rdp options
Some options were missing so they could not be set
in the rdp template.

Closes: #78
2023-09-13 11:27:19 +02:00
Bolke de Bruin
6b32631434 Finalize rdp templating 2023-05-15 10:43:38 +02:00
Bolke de Bruin
cdc497f365 Add templating option for RDP files 2023-05-15 10:43:38 +02:00
Bolke de Bruin
303ed64744 bump koanf 2023-04-16 10:42:16 +02:00
Bolke de Bruin
9d9b7a9ab5 Add test 2023-04-16 10:02:47 +02:00
totomz
cdf6e68684 Use multiple oidc claim to find the username
The clim `preferred_username` is optional in Azure AD. Although is listed as preferred, in some enterprise environment it's not possible to add this additional claim. `unique_name` and `upn` are legacy alternatives
2023-04-07 12:15:06 +02:00
Bolke de Bruin
43eb2d5f47 Make session length configurable 2022-10-22 10:17:43 +02:00
Bolke de Bruin
2abf83f0be Set max session storage to 8kb
If using the filesystem storage provider
for session store it can be set than a larger value than 4kb
as it is not tied to the restriction of a cookie anymore.
2022-10-22 10:08:42 +02:00
Bolke de Bruin
7e3c4abea7 Change name 2022-10-18 11:40:28 +02:00
Bolke de Bruin
ee20553f08 Make stackable 2022-10-18 11:39:26 +02:00
Bolke de Bruin
db98550455 Refactor identity and http routing 2022-10-18 09:36:41 +02:00