fix: language-independent AD principal resolution; auto-provision KDS… - #18
fix: language-independent AD principal resolution; auto-provision KDS…#18FlorianGross wants to merge 2 commits into
Conversation
… Root Key and Windows LAPS schema
Deployments, prerequisite checks and WinLAPS delegations failed on
non-English Active Directory domains (e.g. German: "Domain Admins" is
"Domänen-Admins", "Enterprise Admins" is "Organisations-Admins")
because well-known principals were looked up by their English display
names. All well-known principals are now resolved language-independently
via their fixed SIDs/RIDs. Validated against a real German AD
(full deployment run).
SID/RID resolution (Resolve-TierModelPrincipalSid):
- New WellKnownRid resolution stage that maps canonical names of
well-known domain groups to <DomainSID>-<RID> before any name-based
AD lookup (Guest=501, krbtgt=502, Domain Admins=512, Domain
Users=513, Domain Controllers=516, Cert Publishers=517, GP Creator
Owners=520, RODCs=521, Cloneable DCs=522, Protected Users=525, Key
Admins=526, RAS and IAS Servers=553, Allowed/Denied RODC Password
Replication Group=571/572).
- Forest-level groups (Enterprise RODCs=498, Schema Admins=518,
Enterprise Admins=519, Enterprise Key Admins=527) resolve against the
forest root domain SID, correct also from child domains.
- Well-known SID table extended with all BUILTIN groups (Cryptographic
Operators, Certificate Service DCOM Access, RDS groups, Hyper-V
Administrators, Remote Management Users, Device Owners, User Mode
Hardware Operators, OpenSSH Users, ...) including short-name aliases
as used in tiermodel-gpos.json (e.g. "Backup Operators").
- Domain SIDs are cached per domain controller; on failure the existing
name-based resolution is used as fallback.
- Resolve-ADPrincipalSid: DomainController is now an explicit parameter
instead of relying on dynamic scoping.
Prerequisites (Test-TierModelPrerequisites):
- Domain Admins membership check and Enterprise Admins detection
resolve via RID 512/519 first, with name-based fallback.
GPO deployment:
- New-TierModelGpo: the Deny "Apply Group Policy" ACE is built from a
SecurityIdentifier resolved via Resolve-TierModelPrincipalSid instead
of an NTAccount name, which failed to translate on localized domains
("Some or all identity references could not be translated").
- tiermodel-gpos.json: literal "Power Users" / "User Mode Hardware
Operators" restricted-group entries replaced with their well-known
SIDs (*S-1-5-32-547 / *S-1-5-32-584) so target machines do not have
to resolve English names client-side.
Windows LAPS:
- Get-TierModelWinLapsAcl/Fd resolved readGroup/resetGroup/
decryptorGroup via Get-ADGroup -Filter "Name -eq '...'" — fails for
well-known groups on non-English ADs, leaving allowedPrincipals empty
and crashing Set-LapsADReadPasswordPermission with "Cannot bind
argument ... empty array". Well-known groups are now resolved via
their fixed SID/RID first, with name lookup as fallback.
- New-TierModelWinLapsAcl fails the action with a clear error when a
Read/Reset permission has no resolvable principals instead of
surfacing a parameter binding exception.
Auto-provisioning (execution mode only, -ConfirmApply):
- -IncludeGmsa/-IncludeDmsa: if no KDS Root Key exists, Deploy-TierModel
creates one on the preferred DC via Add-KdsRootKey -EffectiveTime
((Get-Date).AddHours(-10)) so it is effective immediately.
- -IncludeWinLaps: if the msLAPS-* schema attributes are missing, the
AD schema is extended automatically via Update-LapsADSchema (requires
Schema Admins).
- Prerequisites remediation text updated accordingly.
Tests:
- 17 new unit tests for RID/BUILTIN resolution (child domain, caching,
fallback, case-insensitivity) and a prerequisites test simulating a
German AD (group only findable as "Domänen-Admins" via RID 512).
The English group names in the config files remain unchanged and act as
canonical identifiers; no config changes are required for existing
deployments.
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
This PR improves Active Directory tier-model deployments on non-English domains by resolving well-known principals language-independently (SID/RID-based), and adds execution-mode auto-provisioning for optional prerequisites (KDS root key, Windows LAPS schema).
Changes:
- Add RID-based resolution for well-known domain/forest principals (plus expanded BUILTIN well-known SID table) and propagate explicit
-DomainControllerusage through SID resolution. - Update prerequisite checks, WinLAPS planning/execution, and GPO ACL manipulation to avoid localized-name lookups that fail on non-English ADs.
- Extend configuration/test coverage (new unit tests; replace localized restricted-group entries with well-known SIDs in
tiermodel-gpos.json).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Unit.Resolution.Tests.ps1 | Adds unit coverage for RID/BUILTIN resolution, caching, and fallback behaviors. |
| tests/Unit.Prerequisites.Tests.ps1 | Adds a localized-domain (German) prerequisite scenario test for Domain Admin resolution via RID 512. |
| modules/TierModel/public/Test-TierModelPrerequisites.ps1 | Updates DA/EA detection to try RID-based resolution before name lookup; adjusts KDS remediation text. |
| modules/TierModel/public/Resolve-TierModelPrincipalSid.ps1 | Adds well-known RID stage, forest-root SID caching, expands well-known SID table, and makes AD resolution take explicit DC parameter. |
| modules/TierModel/public/New-TierModelWinLapsAcl.ps1 | Adds guardrails to fail clearly when read/reset principals cannot be resolved. |
| modules/TierModel/public/New-TierModelGpo.ps1 | Builds deny “Apply GPO” ACE from resolved SID rather than localized NTAccount translation. |
| modules/TierModel/public/Get-TierModelWinLapsAclFd.ps1 | Updates WinLAPS FD planning group resolution to use SID/RID-first resolution for well-known groups. |
| modules/TierModel/public/Get-TierModelWinLapsAcl.ps1 | Updates WinLAPS planning group resolution to use SID/RID-first resolution for well-known groups. |
| Deploy-TierModel.ps1 | Adds optional prerequisite auto-provisioning (KDS root key, LAPS schema) in execution mode with -ConfirmApply. |
| config/tiermodel-gpos.json | Replaces localized restricted-group names with well-known BUILTIN SIDs for client-side language independence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rom child domains - Get-TierModelDomainSid: query the forest root domain with -Server $forest.RootDomain so a DC of the root domain (within the targeted forest) answers, instead of relying on client-side discovery. - Test-TierModelPrerequisites: the Enterprise Admins existence check now targets a root-domain DC when running from a child domain; previously the SID lookup against the child DC could report HasEnterpriseAdmins as false even though the group exists in the forest root. - Get-TierModelWinLapsAcl/Fd: well-known SIDs are translated locally (SecurityIdentifier -> NTAccount) before falling back to an AD lookup. Translation returns the correct domain prefix even for forest-root groups (e.g. Enterprise Admins) in child domains, avoiding invalid CHILD\<group> principals built from the current domain's NetBIOS name.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
modules/TierModel/public/Get-TierModelWinLapsAcl.ps1:244
- When resolving well-known groups via SID and the local SID→NTAccount translation fails, the fallback path uses "$netBIOSDomain\$sAMAccountName". This produces incorrect principals for BUILTIN groups (should be "BUILTIN\…") and can also be wrong for forest-root groups when running from a child domain (prefix should be the owning domain’s NetBIOS name, not the current domain). This can cause the subsequent Set-LapsAD*Permission calls to target a non-existent group.
if ($adGroup) {
$groupResolution[$group] = "$netBIOSDomain\$($adGroup.sAMAccountName)"
} else {
modules/TierModel/public/Get-TierModelWinLapsAclFd.ps1:194
- Same issue as Get-TierModelWinLapsAcl.ps1: if SID→NTAccount translation fails and the code falls back to Get-ADGroup by SID, the principal is reconstructed using the current domain’s NetBIOS name. This is incorrect for BUILTIN groups (should be "BUILTIN\…") and can be incorrect for forest-root groups when invoked from a child domain.
if ($adGroup) {
$groupResolution[$group] = "$netBIOSDomain\$($adGroup.sAMAccountName)"
}
|
Hello, my German friend! I made the same observation. In my opinion, he should at least document this limitation or add a check: if the operating system’s original installation language is not English, the script should not continue. However, many account and group names are hard-coded throughout the project. A single push will not solve the problem; he needs to review the entire identification strategy, particularly by using SIDs to make the project language-independent. |
|
Please review the language page for more details - https://microsoft.github.io/ActiveDirectoryTierModel/language-support/ I like the idea and it's something we should revisit at a later date; the main feature release is auth silos. In the meantime I will run through the code and come up with some ideas on how we could introduce additional language support, but this would require a community effort to preform testing. |
Deployments, prerequisite checks and WinLAPS delegations failed on non-English Active Directory domains (e.g. German: "Domain Admins" is "Domänen-Admins", "Enterprise Admins" is "Organisations-Admins") because well-known principals were looked up by their English display names. All well-known principals are now resolved language-independently via their fixed SIDs/RIDs. Validated against a real German AD (full deployment run).
SID/RID resolution (Resolve-TierModelPrincipalSid):
Prerequisites (Test-TierModelPrerequisites):
GPO deployment:
Windows LAPS:
Auto-provisioning (execution mode only, -ConfirmApply):
Tests:
The English group names in the config files remain unchanged and act as canonical identifiers; no config changes are required for existing deployments.