Commit Graph

315 Commits

Author SHA1 Message Date
Bolke de Bruin
7d1b9af858 Drop baked-in TLS cert, run as 1001, refuse known placeholder secrets
The dev container image generated a TLS keypair at build time and
shipped it inside the image, so every pull of the same image tag was
serving the same private key. The entrypoint also reverted to USER 0
to support a dead `createusers.txt` loop and a `chmod u+s` that was a
no-op (set on a binary owned by 1001). Net result was that any RCE
in the gateway landed as root and the wire-trust posture relied on a
shared private key.

Stop generating the cert at build time: the runtime image now carries
openssl and the entrypoint mints an ephemeral self-signed cert at
first start when no cert is mounted at the configured path. Each
container instance gets its own key. Drop USER 0 entirely; the
entrypoint runs as 1001 throughout. Prune the dead createusers loop
and the `chmod u+s`.

Separately, the README and the dev compose files publish a small set
of literal placeholder values for SessionKey, SessionEncryptionKey,
and the various Token*Key fields. Operators copy-paste these into
real deployments. Refuse to start when any of those literals appear
in the corresponding config field.
2026-04-30 19:09:15 +02:00
bolkedebruin
de31bfe8a0 Restrict the rdpgw-auth socket to its own UID by default (#190)
The auth daemon's gRPC socket was world-writable and accepted any
local UID that could connect to it. On a multi-tenant host any user
on the box could speak the gRPC API and run an arbitrary username/
password through PAM -- effectively an unauthenticated PAM oracle.

Create the socket with mode 0660 (Umask(0117)) and gate Accept on
SO_PEERCRED: only the daemon's own UID is allowed by default, plus
any operator-supplied --allow-uid / --allow-gid. Privilege-separated
deployments (rdpgw and rdpgw-auth as different users) need to list
the gateway's UID, or share a group; the existing path otherwise
would have been permissive.

The peer-credentials check is Linux-only; the non-Linux build keeps
the listener as-is and logs a warning, since rdpgw-auth itself
requires libpam and is effectively Linux-only in practice.
2026-04-30 18:59:48 +02:00
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
bolkedebruin
449cd1e2fe Gate hostselection=any to public destinations and a port allow-list (#188)
The `roundrobin`, `signed`, and `unsigned` host-selection modes route
requests against an operator-curated `Server.Hosts` list. The `any`
mode does not -- it forwards to whatever `?host=` value the request
carries, which makes the gateway usable as a generic TCP relay
against whatever the gateway can reach (loopback, RFC1918, link-local,
the cloud metadata service, arbitrary high-numbered ports on public
hosts).

Add a small destination policy applied only in `any` mode:

* Reject hosts that resolve to loopback, RFC1918, IPv6 ULA,
  link-local, unspecified, or multicast addresses. Operators can opt
  back in with `Server.AllowPrivateDestinations: true`.
* Restrict the destination port to `Server.AllowedDestinationPorts`
  (default {3389}).

The other host-selection modes are unaffected -- the operator already
curates their hosts list.

The DestinationPolicy zero value is the secure default, so direct
&Handler{} constructions in tests still get the expected behavior.
DNS names are resolved at validation time and every returned address
is checked.
2026-04-30 18:42:24 +02:00
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
dependabot[bot]
a6d9379dc0 Bump google.golang.org/grpc from 1.62.1 to 1.79.3 (#174)
* Bump google.golang.org/grpc from 1.62.1 to 1.79.3

Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.1 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.62.1...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Resolve latest master merge conflicts in go.mod

Agent-Logs-Url: https://github.com/bolkedebruin/rdpgw/sessions/91a690a5-0236-461d-bf56-cada31670684

Co-authored-by: bolkedebruin <4282712+bolkedebruin@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bolkedebruin <4282712+bolkedebruin@users.noreply.github.com>
2026-04-23 20:05:49 +02:00
dependabot[bot]
a5dfc1b949 Bump github.com/go-jose/go-jose/v3 from 3.0.4 to 3.0.5 (#177)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-version: 3.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 19:35: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
dependabot[bot]
a93c9b438c Bump github.com/go-jose/go-jose/v4 from 4.0.5 to 4.1.4 (#178)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.5 to 4.1.4.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.5...v4.1.4)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-version: 4.1.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 13:19:16 +02:00
dependabot[bot]
3b96fa26fe Bump golang.org/x/crypto from 0.36.0 to 0.45.0 (#167)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.36.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.36.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 09:31:56 +01:00
Bolke de Bruin
1d35d6ede0 detect html as not being authenticated anymore 2025-09-26 19:18:47 +02:00
Bolke de Bruin
fd5b89a39c Add extra on mstsc 2025-09-26 00:25:58 +02:00
Bolke de Bruin
a4fc955fe4 Redirect when authentication expires 2025-09-26 00:25:44 +02:00
Bolke de Bruin
55f528ae15 Fix docker file again 2025-09-25 16:15:02 +02:00
Bolke de Bruin
3f73572bcc Fix docker file 2025-09-25 16:02:31 +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
bolkedebruin
e5302e3795 Install CA certificates in Dockerfile stages 2025-09-23 14:43:17 +02:00
Bolke de Bruin
8c4543b109 remove auto gen stuuf 2025-09-18 23:11:31 +02:00
Bolke de Bruin
866ed46fdc Extra docs 2025-09-18 23:09:40 +02:00
Bolke de Bruin
46d12c52be Add extra info on app gateway 2025-09-18 23:03:46 +02:00
Bolke de Bruin
75a7ca62a9 Add header authentication v2.2.0 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>
v2.1.0
2025-09-05 15:10:48 +02:00
dependabot[bot]
d8c483f9f9 Bump golang.org/x/oauth2 from 0.18.0 to 0.27.0 (#151)
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.18.0 to 0.27.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.18.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-version: 0.27.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 15:03:39 +02:00
Bolke de Bruin
85fec5fb2a fix: improve ios compatibility 2025-09-05 15:02:57 +02:00
Bolke de Bruin
c99b4ee58b fix: make docker build again 2025-09-05 14:39:45 +02:00
Bolke de Bruin
611d1bfd15 fix: ignore non project files 2025-09-05 14:38:59 +02:00
Bolke de Bruin
232f70f155 fix: linting issue 2025-09-05 14:31:15 +02:00
dependabot[bot]
4241f63d8e Bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 (#155)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 14:22:19 +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
Chao-Jui Chang
10722d7105 Add tzdata package for showing local time in log (#149) 2025-07-15 12:08:50 +02:00
dependabot[bot]
a8ae18fe85 Bump github.com/go-viper/mapstructure/v2 from 2.0.0-alpha.1 to 2.3.0 (#148)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.0.0-alpha.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.0.0-alpha.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-15 12:05:59 +02:00
dependabot[bot]
caa765371d Bump golang.org/x/net from 0.23.0 to 0.38.0 (#142)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-06 17:38:51 +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
dependabot[bot]
a687557e69 Bump github.com/go-jose/go-jose/v4 from 4.0.1 to 4.0.5 (#136)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.1 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.1...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 15:05:56 +01:00
dependabot[bot]
83e6fe0487 Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.4 (#138)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.4.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.4)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-27 15:05:45 +01:00
Firman Alamsyah
80604075d0 docs: Add link to docker hub (#128)
Added link to docker hub
2025-01-15 23:24:19 +01:00
dependabot[bot]
c8312348fd Bump golang.org/x/crypto from 0.21.0 to 0.31.0 (#130)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-15 23:23:58 +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
dependabot[bot]
7472c7b2c1 Bump golang.org/x/net from 0.22.0 to 0.23.0 (#111)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 22:16:43 +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 v2.0.2 2024-03-30 12:12:55 +01:00