Follow LDAP referrals for write operations in multi-DC forests - #11
Open
j4s0nmo0n wants to merge 1 commit into
Open
Follow LDAP referrals for write operations in multi-DC forests#11j4s0nmo0n wants to merge 1 commit into
j4s0nmo0n wants to merge 1 commit into
Conversation
ADWS returns Win32ErrorCode 8235 (ERROR_DS_REFERRAL) when a DC does not host the partition targeted by a request. Previously this surfaced as an opaque XML dump and killed the operation. This change makes the ADWS client swap to the referred DC and retry, transparently to callers. Design: * ADWSConnect._swap_endpoint(target_dc, hops) reconnects the NMF transport to another DC, updates self._fqdn (used in SOAP payloads), and is bounded by ADWSConnect.MAX_REFERRAL_HOPS (default 3) to prevent loops on misconfigured forests. * ADWSConnect.put() catches ADWSReferralError, swaps, rebuilds the payload with the new fqdn, and retries. This transparently covers --rbcd, --asrep, --spn, --disable-account, --dns-modify, --dns-remove, --dns-tombstone, --dns-resurrect, and --shadow-creds add/remove. * ad_dns_manager_adws._pull_with_referral_follow wraps pull_client.pull with the same retry pattern. Used by find_dns_node, add_dns_record's dnsZone enumeration, and shadow_credentials target discovery. * add_dns_record_adws, remove_dns_record_adws, and _set_dnstombstoned have their raw SOAP send/recv wrapped with a retry loop that rebuilds the payload after each swap. * add_dns_record_adws and add_computer proactively discover the DC that hosts the target container (via a lightweight pull) and route the subsequent Create there, avoiding CouldntFindParentObjectForCreation which AD returns instead of a referral when the parent DN doesn't exist on the queried DC. * getAccountDN can now return the fqdn of the DC that resolved the account, letting delete_computer route the Delete to the right DC. Scope: write operations only. Enumerations (--users, --computers, --groups) require restarting the whole Enumerate+Pull cycle after swap because the enumeration context is invalidated; that is out of scope here and will be addressed separately. Tested end-to-end in a parent/child forest (jjk.local / modulo.jjk.local): * --dns-add creates the record on the child DC via the parent DC * --shadow-creds list/add reach the target across the referral * --addcomputer creates the machine account in the child domain from the parent DC
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.
Hello Logan
Context
In a multi-DC forest, an ADWS request can hit a DC that does not host the target partition. In that case, ADWS answers with
Win32ErrorCode 8235(ERROR_DS_REFERRAL) and points to another DC. Before this change, the client surfaced the response as a raw XML dump and the operation died there.This PR makes the
ADWSConnecttransport swap to the referred DC and retry, transparently to callers. Scope is intentionally limited to write operations; enumerations (--users,--computers,--groups) are a separate problem because the enumeration context is invalidated after a swap and the wholeEnumerate + Pullcycle needs to be restarted.I plan to send that as a follow-up PR.
Design
ADWSConnect._swap_endpoint(target_dc, hops)- reconnects the NMF transport to another DC, updatesself._fqdn(embedded in SOAP payloads), bounded byMAX_REFERRAL_HOPS(default 3) to avoid loops on misconfigured forests.ADWSConnect.put()- catchesADWSReferralError, swaps, rebuilds the payload with the newfqdn, retries. Transparently covers--rbcd,--asrep,--spn,--disable-account,--dns-modify,--dns-remove,--dns-tombstone,--dns-resurrect,--shadow-creds add/remove._pull_with_referral_follow(pull_client, query, basedn, attributes)- small helper inad_dns_manager_adws.pythat wrapspull_client.pull()with the same retry pattern. Used byfind_dns_node,add_dns_record_adws's dnsZone enumeration,shadow_credentials, andgetAccountDN.Raw SOAP send/recv sites -
add_dns_record_adws(Create),remove_dns_record_adws(Delete), and_set_dnstombstoned_replace_boolean(Put tombstone) construct their own SOAP payloads outside ofput(). Each is wrapped with a retry loop that rebuilds the payload with the updatedfqdnafter each swap.Proactive DC discovery - for
add_dns_record_adwsandadd_computer, a lightweight pull on the target container discovers which DC hosts the partition, and the subsequent Create is routed to that DC directly. This avoidsCouldntFindParentObjectForCreation, which AD returns instead of a referral when the parent DN doesn't exist on the queried DC (as opposed to a partition it knows about but doesn't host).getAccountDN(return_target_fqdn=True)- optionally returns the fqdn of the DC that resolved the account.delete_computeruses this to route the Delete to the DC that actually holds the object.Tested end-to-end
Parent/child forest, using an account from the child domain against the parent DC:
Same DC and account,
--shadow-creds list --shadow-target ino:Same DC and account,
--addcomputer:Notes
MAX_REFERRAL_HOPSis exposed as a class attribute so it can be tuned if needed./etc/hostsor-dc, instead of the previous opaqueWin32ErrorCode 8235.socket.gethostbyname.--users,--computers,--groups) still bubble upADWSReferralErrorcleanly (with a readable message), just not automatically followed. Follow-up PR planned.Happy to split, rework, or narrow the scope if you'd prefer a smaller diff to review.