Fix LDAP base DN and entryUUID handling for hosted directories - #2546
Draft
leandro-lorenzini wants to merge 4 commits into
Draft
Fix LDAP base DN and entryUUID handling for hosted directories#2546leandro-lorenzini wants to merge 4 commits into
leandro-lorenzini wants to merge 4 commits into
Conversation
`api_update_ldap_server` ran `discover_base_dns` unconditionally and assigned the result over `model.base_dns`, and `UpdateLdapServerRequest` had no `base_dns` field at all. Base DNs therefore could not be set through the admin UI: whatever the RootDSE advertised replaced them on every save, and the UI only rendered them as a read-only "Base DNs (discovered)" list. That is fine for a directory whose RootDSE names a searchable base, but not for hosted multi-tenant LDAP. JumpCloud, for example, advertises `dc=jumpcloud,dc=com` and `ou=service-accounts` as naming contexts; neither is searchable by a tenant, and searching them fails with `rc=32 (noSuchObject)`. The tenant's real base (`ou=Users,o=<org id>,dc=jumpcloud,dc=com`) is derivable only from the bind DN, so discovery cannot produce it and the operator has no way to supply it. The only workaround was editing the database directly. Add an optional `base_dns` to the update request and honour it when it contains at least one non-blank entry, falling back to discovery otherwise, so clearing the list still re-detects. Make the UI field an editable list that round-trips the value on save. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
leandro-lorenzini
marked this pull request as draft
September 4, 2026 08:43
With `uuid_attribute` unset, `extract_ldap_user` only looked in
`bin_attrs`:
```rust
search_entry
.bin_attrs
.get("objectGUID")
.or_else(|| search_entry.bin_attrs.get("entryUUID"))
```
Active Directory returns `objectGUID` as 16 raw bytes, so that works.
`entryUUID` is a dashed string, which ldap3 decodes as UTF-8 into
`attrs`, so the lookup never matches and extraction fails with
`NoUUID` — despite the comment saying OpenLDAP is covered.
`find_user_by_filter` logs that as a warning and returns `Ok(None)`, so
the caller reports "no user found" for an entry the directory did
return. Against JumpCloud, which serves `entryUUID` as a string, this
made auto-link fail with `No LDAP user found with username: <name>`
while the identical `ldapsearch` returned the entry.
Fall back to parsing `entryUUID` from `attrs`, mirroring what the
configured-attribute branch above already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`api_create_ldap_server` always ran discovery and propagated its error with `?`, so a directory that does not advertise a usable naming context could not be registered at all — the request failed before the row was written, leaving nothing to correct on the detail page. Accept the same optional `base_dns` the update endpoint now takes and skip discovery when it is provided, so callers that already know their search base are not blocked by the RootDSE. Behaviour is unchanged when the field is omitted. The create page itself still relies on discovery; base DNs remain editable afterwards on the server's detail page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`a_string_entry_uuid_is_read_when_no_uuid_attribute_is_configured` fails on the previous `bin_attrs`-only lookup and passes with the fallback, so it pins the regression rather than just describing it. The other three guard the paths that already worked: a binary `objectGUID`, a configured attribute holding a string, and an entry with no UUID at all still being rejected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Three related defects that make Warpgate's LDAP integration unusable against hosted multi-tenant directories. All were hit configuring JumpCloud Cloud LDAP; each is reproducible on stock OpenLDAP too, and the third affects any directory that doesn't publish a searchable naming context.
The common thread: base DNs and UUIDs are treated as things the server discovers, never as things an operator can state.
1. Base DNs are overwritten on every save
api_update_ldap_servercalleddiscover_base_dnsunconditionally and assigned overmodel.base_dns, andUpdateLdapServerRequesthad nobase_dnsfield. Base DNs simply could not be set — the RootDSE won every save, and the UI showed them read-only as "Base DNs (discovered)".JumpCloud advertises
dc=jumpcloud,dc=comandou=service-accountsas naming contexts. Neither is searchable by a tenant:The tenant's real base is
ou=Users,o=<org id>,dc=jumpcloud,dc=com, and the org id appears only in the bind DN — discovery cannot derive it. The only workaround wasUPDATE ldap_servers SET base_dns = ..., which the next save silently undid.Fix: optional
base_dnson the update request, honoured when it holds at least one non-blank entry, discovery otherwise — so clearing the list still re-detects. The UI list becomes editable (same pattern asAllowedIpRangesEditor) and round-trips on save.2. A string-valued
entryUUIDis never readWith
uuid_attributeunset,extract_ldap_userlooked only inbin_attrs:AD returns
objectGUIDas 16 raw bytes, so that path works.entryUUIDis a dashed string, which ldap3 decodes as UTF-8 intoattrs— so the lookup never matches and extraction fails withNoUUID, despite the comment claiming OpenLDAP is covered.find_user_by_filterturns that into a warning andOk(None), so the caller reports no user found for an entry the directory did return. Auto-link failed withNo LDAP user found with username: <name>while an identicalldapsearchreturned the entry — a confusing place to land, since the message points at the username rather than at UUID parsing.Fix: fall back to parsing
entryUUIDfromattrs, mirroring what the configured-attribute branch already does.3. A server whose RootDSE is unusable can't be created
api_create_ldap_serverpropagated discovery failure with?, so registration failed before the row existed — leaving nothing to correct on the detail page.Fix: accept the same optional
base_dnsand skip discovery when provided. Unchanged when omitted.Compatibility
Additive. Both new fields are optional and omitting them preserves today's behaviour, so existing clients and the generated SDKs are unaffected.
Testing
Diagnosed against a live JumpCloud tenant; the base DN reverting on every save is what led to the code. Confirmed
ldapsearchwith the explicit base returns users that discovery's DNs cannot, and that JumpCloud servesentryUUIDas a string (94708f40-…), which is what surfaced defect 2.Verified locally on the pinned
nightly-2026-07-09:cargo check -p warpgate-ldap -p warpgate-admincargo cranky --workspace --all-features(whatjust clippyruns)biome cion the changed Svelte file (2.5.9, as pinned)openapi-schema.jsonwas edited by hand first, then checked by regenerating it withcargo run -p warpgate-adminand diffing: the output is byte-identical apart from theversionfield, which tracks the build's git describe. I kept the hand-edited file so that field isn't churned.The workspace cranky run does emit 11 warnings, all pre-existing and in other crates — four manifest-level unused-dependency notes, a
map_orsimplification and nine other lints inwarpgate-protocol-http, and a future-incompat note fromproc-macro-error2. None are introduced here.Deliberately not included
The create page still relies on discovery — base DNs stay editable afterwards on the detail page, and adding a second editor there seemed like scope creep. Happy to add it if you'd rather the forms match.
AI Usage
Choose the level of AI involvement for this PR.
This is not to block AI contributions but rather to speed up PR review (saves time on trying to deduce the logic behind AI hallucinations).
All three defects were hit during a real JumpCloud deployment and confirmed against the source and a live directory before patching; the code itself is AI-written, then compiled and linted with the project's own toolchain and lint config as described above.