feat(dgw): provision the target-side KDC for credential injection - #1895
Conversation
8eb17fb to
6ea4f50
Compare
6ea4f50 to
81c211d
Compare
03d180d to
28ecdf1
Compare
9bc4966 to
8e03f70
Compare
There was a problem hiding this comment.
Pull request overview
Adds target-side KDC provisioning for Kerberos credential injection.
Changes:
- Adds validated connection options and a preflight provisioning operation.
- Splits credential and connection-option storage with independent expiry.
- Routes target-side Kerberos requests through the provisioned KDC.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
tests/preflight.rs |
Tests provisioning operations. |
src/target_connection_options.rs |
Defines validated KDC options. |
src/target_addr.rs |
Adds URL conversion. |
src/rdp_proxy.rs |
Configures target-side Kerberos. |
src/provisioning.rs |
Stores credentials and options independently. |
src/lib.rs |
Registers the new module. |
src/kdc_connector.rs |
Shares supported KDC schemes. |
src/credential_injection_kdc.rs |
Carries options into session state. |
src/api/preflight.rs |
Adds the provisioning operation. |
Comments suppressed due to low confidence (1)
devolutions-gateway/src/rdp_proxy.rs:405
- The core new routing behavior is not covered by the existing tests in this file: they only exercise
injection_uses_kerberos, while no test verifies that a provisioned KDC becomes the clientkdc_proxy_url(or that Kerberos rejects a missing KDC). Add focused coverage for both cases to prevent the target-side wiring from regressing.
Ok(CredentialInjectionKerberosConfigs {
server: Some(credential_injection_kdc.server_kerberos_config(client_addr)?),
client: Some(ironrdp_connector::credssp::KerberosConfig {
kdc_proxy_url: Some(url::Url::try_from(krb_kdc).context("convert target KDC address to URL")?),
hostname: gateway_hostname.to_owned(),
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ee07faa to
001b470
Compare
0a7a48a to
0f5bbe0
Compare
0f5bbe0 to
33c018a
Compare
Kerberos credential injection needs the real KDC of the target's domain, and the Gateway has no way to discover it: the target-side CredSSP leg passed no KDC proxy URL at all, so a Kerberos session could never complete and only NTLM worked. Let the caller provision it. `TargetConnectionOptions` is the second thing a session can carry alongside its credentials, validated at construction — supported scheme, a host, a parseable URL — so a bad address is rejected at provisioning time instead of failing when the session starts. The CredSSP client leg now takes its KDC from there. The spec and clients are regenerated in the next commit.
Credentials and target connection options are independent provisioning events with different trust boundaries. Back them with two JTI-keyed stores and expose provision-connection-options alongside provision-credentials. provision-token stays validate-and-ack only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
provision-token still inserts a token-only row. It shares the credentials insert path with provision-credentials again. Only connection-options is the new separate provision op. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Runtime dual-op does not need hand-edited utoipa schemas. Spec and clients land in the openapi regen layer on top of this branch.
After #1856, kdc_for uses ConfHandle hostname for TERMSRV SPN, not association-token dst_hst. Update the dual-op unit test accordingly.
Lowercase error/context strings and point credential docs at insert_credentials. OpenAPI for the new preflight op stays in the dedicated regen PR above this layer.
33c018a to
1b3cfd0
Compare
Benoît Cortier (CBenoit)
left a comment
There was a problem hiding this comment.
Approving for velocity, but consider fixing these issues
| #[derive(Deserialize)] | ||
| struct RawTargetConnectionOptions { | ||
| #[serde(default)] | ||
| krb_kdc: Option<TargetAddr>, |
There was a problem hiding this comment.
This only proves that the serialized TargetAddr can be parsed as a URL; it does not prove both parsers describe the same endpoint. For example, tcp://dc.example/path:88 becomes a TargetAddr whose host is dc.example/path, while Url treats /path:88 as a path. Provisioning ACKs it, but the later KDC connector reparses it and attempts to resolve the malformed host. Please validate a URL first and reject user info, paths, queries, and fragments before deriving the TargetAddr.
Note
Human-tuned, LLM-assisted content.
There was a problem hiding this comment.
Fixed: krb_kdc is now parsed as a Url first. We require scheme tcp/udp, a host, an explicit port, and reject userinfo/path/query/fragment before storing it. CredSSP receives that Url directly.
Note
LLM-assisted content (no human feedback).
| fn try_from(target: &TargetAddr) -> Result<Self, Self::Error> { | ||
| url::Url::parse(target.as_str()) | ||
| } | ||
| } |
There was a problem hiding this comment.
I do not think this conversion belongs on TargetAddr: it is only needed for the KDC option, and TargetAddr and Url accept different syntax. Validate and retain a Url in TargetConnectionOptions instead, then pass it to CredSSP directly.
Note
Human-tuned, LLM-assisted content.
There was a problem hiding this comment.
Fixed: dropped TryFrom<&TargetAddr> for Url. TargetConnectionOptions keeps a validated Url and passes it straight to CredSSP.
Note
LLM-assisted content (no human feedback).
TargetAddr and Url disagree on hosts with embedded paths, so a round-trip parse was not enough. Parse krb_kdc as a Url first, reject userinfo/path/query/fragment, store the Url, and pass it to CredSSP directly. Drop the TargetAddr-to-Url conversion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover the provision → kdc_for → credential_injection_kerberos_configs path so a missing or dropped target KDC cannot regress silently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0e91bbf
into
master
This PR add OP_PROVISION_CONNECTION_OPTIONS, that is, for now, just kdc in there.
Then uses it, construct kdc sessions in each handler on demand.
It is already complete in terms of functionality, but I do believe that the bondary is starting to blur.
Most importantly
CredentialServiceis taking more responsibility than it should.