Devolutions / Devolutions/IronRDP

Server-side credential handling: a design frame for the validate-on-receipt deployment class

Open
#1,357 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
3.2k
Forks
275
Avg merge
1d 11h
Merged PRs (30d)
189

Description

IronRDP's server stack has grown several credential- and transport-handling threads, each scoped to a concrete PR or issue. Together they describe one coherent design axis, and naming it explicitly would let the pieces land as a single shape rather than independently. This is in the spirit of the architecture-direction issues you've started writing down (#1349, #1352), applied to the server's credential and transport surface.

### Two server deployment classes

`ironrdp-server` today serves the **credential-injection / proxy** class well: the server holds the credentials and either exact-matches them at the acceptor or runs CredSSP via a pre-loaded `CredentialsProxy`. Devolutions Gateway is the canonical consumer, driving `ironrdp_acceptor`'s CredSSP (server role, facing the client) alongside `ironrdp_connector`'s CredSSP (client role, facing the target) to inject vault credentials at both ends. For that class the model is exactly right.

The second class is **validate-on-receipt**: a standalone or infrastructure RDP server that authenticates the connecting party itself, an end user against PAM/LDAP/AD or a backend access layer against a capability token. The spec permits this and locates it precisely:

- credentials arrive in the Client Info PDU (MS-RDPBCGR 2.2.1.11) on the Standard (§5.3.2) and Enhanced/TLS (§5.4.1) security paths, after the channel is secured;
- CredSSP/NLA (MS-CSSP) is a *pre-session* challenge-response that needs a password-equivalent secret in hand, so it structurally cannot validate against an opaque backend;
- MS-RDP leaves server-side authorization *policy* to the implementer, so validate-on-receipt is in-spec; `ironrdp-server` just doesn't name it as a supported shape yet.

This need recurs from outside Lamco as well: #508 ("How can I check RDP credentials?") was answered with the stop-after-NLA / CredSSP route, which is the injection/mechanism path and only works when the secret is already known.

### The in-flight pieces are instances of the second class

- **Credential validation** — #1154 / #1172 (`CredentialValidator`): validate Client Info PDU credentials on the TLS-mode path. The validation-policy counterpart to the injection path; different security modes, not competing mechanisms (the tell: `pam` + `hybrid` is incompatible by construction). The safe-default-reject design also addresses the explicit-opt-in-for-no-auth concern raised on #1150.
- **Pre-authenticated transport** — #1281 (`run_connection_pre_authenticated`): accept a stream whose TLS was terminated by an outer layer (WSS / RDCleanPath) for proxy-fronted backends. The constraint is concrete: the closed PR #1210 showed that advertising `PROTOCOL_SSL` while skipping the handshake breaks vanilla clients, so the pre-auth path must not touch X.224 negotiation and relies on RDCleanPath as the "TLS happened above" signal.
- **Conformant server protocol behind a proxy** — #1346 / #1347 / #1348 (NetworkAutoDetect over the MCS message channel, message-channel negotiation): correct server behavior the backend/proxied deployment depends on.
- **Runtime-agnostic serving** (longer-term): `ARCHITECTURE.md` states the I/O invariant ("doing I/O is not allowed for these crates") and lists `ironrdp-client`, `ironrdp-web`, and `ironrdp-async` as the I/O-permitted crates, yet `ironrdp-server` is the most I/O-coupled crate in the workspace and is absent from that list. Extending the existing sans-I/O discipline (core tier + the client-side `-tokio`/`-blocking`/`-futures` split) to the server would make deterministic testing, alternative runtimes, and the WebSocket+RDCleanPath path natural. This is gated behind the in-flight `anyhow` -> typed-error migration (#1209), so it is a later step, noted here for coherence rather than proposed now.

### The design frame

Treat validate-on-receipt as a recognized server deployment class, so these threads are designed against one frame rather than piecemeal:

- credential validation is **async and runtime-agnostic** (the hook fires once per connection, so the cost is negligible and the blocking offload belongs in the implementor), and object-safe for optional pluggability;
- **rejection is spec-defined** (`ServerSetErrorInfoPdu`) across both the exact-match and validator paths, so behavior does not diverge by path;
- the **transport seam** (pre-authenticated / RDCleanPath) and the **validation seam** compose, so a single `RdpServer` can serve direct-TLS clients and proxy-fronted backends;
- none of this touches the **injection / CredSSP path**, which the proxy class keeps using unchanged.

This is not a proposal to redirect IronRDP's architecture. It is a frame that makes the existing PRs coherent and gives one place to discuss the server's I/O posture as a deliberate choice. #1172 is the immediate concrete step; I'm answering the open design questions there and pointing back here for the broader shape.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.