$ErrorActionPreference = 'Stop' $repo = Split-Path $PSScriptRoot -Parent $script:ScriptRoot = $repo . (Join-Path $repo 'scripts/Configuration.ps1') . (Join-Path $repo 'scripts/AdObjectSacl.ps1') $script:checks = 0 function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:checks++ } function Assert-Throws([scriptblock]$Action, [string]$Message) { $thrown = $false; try { & $Action } catch { $thrown = $true }; Assert $thrown $Message } $root = Join-Path ([IO.Path]::GetTempPath()) ('wela-ad-sacl-' + [guid]::NewGuid().ToString('N')) $null = New-Item -ItemType Directory -Path $root $session = [pscustomobject]@{ Server = 'dc1.example.test'; DomainDn = 'DC=example,DC=test'; ConfigurationDn = 'CN=Configuration,DC=example,DC=test'; SchemaDn = 'CN=Schema,CN=Configuration,DC=example,DC=test'; Writable = $true } $script:originalDmsa = ${function:Test-WelaAdDmsaDomain} $script:originalSchema = ${function:Test-WelaAdSchemaClass} $script:originalRead = ${function:Get-WelaAdObjectState} $script:originalSearch = ${function:Search-WelaAdDirectory} $script:originalPresent = ${function:Test-WelaAdAcePresent} $script:originalAddition = ${function:New-WelaAdSaclAddition} $script:originalWrite = ${function:Write-WelaAdSacl} $script:originalInfo = ${function:Get-WelaAdDescriptorInfo} function Reset-Mocks { $script:writes = 0; $script:reads = 0; $script:readError = $false; $script:writeError = $false $script:race = $false; $script:finalDrift = $false; $script:badReadback = $false; $script:removeExisting = $false $script:absentClass = ''; $script:dmsa = $true; $script:exchange = $true; $script:onPrompt = $null; $script:pkiMember = $true; $script:pkiRequest = $null $script:dmsaError = ''; $script:schemaErrorClass = ''; $script:plannedAdditions = @() $script:state = [pscustomobject]@{ Server = $session.Server; Dn = $session.DomainDn; ObjectGuid = '01234567-89ab-cdef-0123-456789abcdef'; UsnChanged = '17'; Classes = @('top', 'domainDNS'); Descriptor = [pscustomobject]@{ Binary = 'before'; Sddl = 'O:SYG:SYD:(A;;GA;;;SY)S:(AU;SA;WP;;;BA)'; Owner = 'S-1-5-18'; Group = 'S-1-5-18'; Dacl = 'unchanged-dacl'; ControlFlags = 32788; Sacl = @('unrelated') } } $session.Writable = $true } function Test-WelaAdSchemaClass { param($Session, $Class, $Guid) if ($Class -eq $script:schemaErrorClass) { throw 'Schema lookup access denied' } return $Class -ne $script:absentClass } function Test-WelaAdDmsaDomain { param($Session) if ($script:dmsaError) { throw $script:dmsaError } return $script:dmsa } function Search-WelaAdDirectory { param($Session, $Dn, $Filter, $Scope, $Attributes, [switch]$SecurityDescriptor) if ($Filter -eq '(objectClass=msExchOrganizationContainer)' -and $script:exchange) { [pscustomobject]@{ Dn = 'CN=Exchange'; Values = @{} } } if ($Filter -like '(objectGUID=*') { $script:pkiRequest = @{ Dn = $Dn; Scope = $Scope; Filter = $Filter } if ($script:pkiMember) { [pscustomobject]@{ Dn = $script:state.Dn; Values = @{ objectGUID = @(,([guid]$script:state.ObjectGuid).ToByteArray()) } } } } } function Get-WelaAdObjectState { param($Session, $Dn) $script:reads++ if ($script:readError) { throw 'LDAP access denied' } $snapshot = $script:state | ConvertTo-Json -Depth 10 | ConvertFrom-Json if ($script:race -and $script:reads -ge 3) { $snapshot.UsnChanged = '99' } if ($script:finalDrift -and $script:reads -ge 6) { $snapshot.Descriptor.Sacl = @('unrelated'); $snapshot.Descriptor.Binary = 'drift' } return $snapshot } function Test-WelaAdAcePresent { param($Descriptor, $Definition) return $Descriptor.Sacl -contains ('added-' + $Definition.Class) } function New-WelaAdSaclAddition { param($Before, $Definitions) $script:plannedAdditions = @($Definitions | Where-Object { -not (Test-WelaAdAcePresent $Before.Descriptor $_) } | ForEach-Object { 'added-' + $_.Class }) [pscustomobject]@{ Binary = 'after'; AddedAces = $script:plannedAdditions } } function Write-WelaAdSacl { param($Session, $Dn, $Binary) if ($script:writeError) { throw 'LDAP insufficientAccessRights' } $script:writes++ if (-not $script:badReadback) { $script:state.Descriptor.Sacl = @($script:state.Descriptor.Sacl) + $script:plannedAdditions $script:state.Descriptor.Binary = $Binary } if ($script:removeExisting) { $script:state.Descriptor.Sacl = @($script:state.Descriptor.Sacl | Where-Object { $_ -ne 'unrelated' }) } $script:state.UsnChanged = '18' } function Read-Host { param($Prompt) if ($script:onPrompt) { & $script:onPrompt }; return 'y' } function Get-Context([bool]$Dry = $false, [bool]$Automatic = $true) { New-WelaConfigurationContext -Auto:$Automatic -DryRun:$Dry -BackupPath (Join-Path $root ([guid]::NewGuid().ToString('N'))) } function Invoke-TestConfigure($Context) { $plan = @(Get-WelaAdSaclPlan $session @('MdiDomain') @()) Set-WelaAdSaclControls $session $Context $plan Complete-WelaConfiguration -Context $Context -Scope ad-object-sacl-only } try { $defs = @(Get-WelaAdAuditDefinitions) Assert ($defs.Count -eq 6) 'all six MDI descendant classes are defined' Assert ((@($defs.AccessMask) -join ',') -eq '852331,852331,852331,852331,852075,852075') 'exact Microsoft readiness masks, including gMSA/dMSA distinction' Assert (@($defs | Where-Object { $_.Sid -ne 'S-1-1-0' -or $_.AceFlags -ne 74 -or $_.Inheritance -ne 'DescendantsOnly' -or $_.ObjectType -ne [guid]::Empty.ToString() }).Count -eq 0) 'success, descendant-only, unrestricted property scope is explicit' Assert (($defs.InheritedObjectType | Select-Object -Unique).Count -eq 6) 'all inherited class GUIDs differ' Reset-Mocks $plan = @(Get-WelaAdSaclPlan $session @('MdiDomain') @()) Assert ($plan.Count -eq 1 -and $plan[0].Definitions.Count -eq 6 -and $plan[0].Status -eq 'ChangeRequired') 'domain plan covers exact root' Assert ($plan[0].Server -eq $session.Server -and $plan[0].Dn -eq $session.DomainDn) 'plan binds exact server and DN' $script:dmsa = $false $plan = @(Get-WelaAdSaclPlan $session @('MdiDomain') @()) Assert ($plan[0].Definitions.Count -eq 5 -and $plan[0].Diagnostic -like '*dMSA skipped*') 'dMSA omitted without a 2025 domain DC' Assert ($plan[0].SkippedDefinitions[0].PrerequisiteStatus -eq 'NotApplicable') 'known dMSA omission is distinct from unknown prerequisites' $script:absentClass = 'user' Assert ((@(Get-WelaAdSaclPlan $session @('MdiDomain') @()))[0].Status -eq 'Unknown') 'missing required class blocks writes' foreach ($errorMessage in @('DC versions could not all be classified; dMSA applicability is unknown.', 'LDAP DC version query access denied')) { Reset-Mocks; $script:dmsaError = $errorMessage $plan = @(Get-WelaAdSaclPlan $session @('MdiDomain') @()) Assert ($plan[0].Status -eq 'ChangeRequired' -and $plan[0].Definitions.Count -eq 5) 'unknown optional dMSA check preserves five independent planned ACEs' Assert ($plan[0].SkippedDefinitions.Count -eq 1 -and $plan[0].SkippedDefinitions[0].PrerequisiteStatus -eq 'Unknown' -and $plan[0].SkippedDefinitions[0].Diagnostic.Contains($errorMessage)) 'plan records the skipped dMSA prerequisite and exact unknown diagnostic' $ctx = Get-Context; $report = Invoke-TestConfigure $ctx $applied = @($report.Results | Where-Object Kind -eq 'AdObjectSacl') $gap = @($report.Results | Where-Object Kind -eq 'AdObjectSaclPrerequisite') Assert ($report.ExitCode -eq 0 -and $report.Skipped -eq 1 -and $applied[0].Status -eq 'Applied' -and $applied[0].Desired.Count -eq 5) 'five ACEs configure with a visible skipped-control result' Assert ($gap.Count -eq 1 -and $gap[0].Status -eq 'Skipped' -and $gap[0].PrerequisiteStatus -eq 'Unknown' -and $gap[0].Target.Class -eq 'msDS-DelegatedManagedServiceAccount') 'configuration retains structured dMSA Unknown gap' Assert ($script:plannedAdditions.Count -eq 5 -and $script:state.Descriptor.Sacl -notcontains 'added-msDS-DelegatedManagedServiceAccount') 'unknown dMSA never produces a sixth ACE write' } Reset-Mocks; $script:schemaErrorClass = 'msDS-DelegatedManagedServiceAccount' $plan = @(Get-WelaAdSaclPlan $session @('MdiDomain') @()) Assert ($plan[0].Status -eq 'ChangeRequired' -and $plan[0].Definitions.Count -eq 5 -and $plan[0].SkippedDefinitions[0].PrerequisiteStatus -eq 'Unknown') 'optional dMSA schema read error also remains isolated' Reset-Mocks; $script:schemaErrorClass = 'user' $ctx = Get-Context; $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $script:writes -eq 0 -and $report.Results[0].Status -eq 'Failed') 'mandatory schema lookup failure still fails closed' Reset-Mocks; $script:exchange = $false Assert ((@(Get-WelaAdSaclPlan $session @('MdiConfiguration') @()))[0].Status -eq 'NotApplicable') 'configuration gated on Exchange history evidence' $script:exchange = $true $plan = @(Get-WelaAdSaclPlan $session @('MdiConfiguration') @()) Assert ($plan[0].Definitions[0].AccessMask -eq 32 -and $plan[0].Definitions[0].AceFlags -eq 194) 'Configuration WriteProperty success/failure inheritance is exact' Assert-Throws { Get-WelaAdSaclPlan $session @('PkiObjects') @() } 'PKI requires explicit target DNs' $script:state.Dn = "CN=Test,CN=Certificate Templates,CN=Public Key Services,CN=Services,$($session.ConfigurationDn)" $script:state.Classes = @('top', 'pKICertificateTemplate') $plan = @(Get-WelaAdSaclPlan $session @('PkiObjects') @($script:state.Dn)) Assert ($plan[0].Status -eq 'ChangeRequired' -and $plan[0].Definitions[0].AccessMask -eq 852000 -and $plan[0].Definitions[0].AceFlags -eq 64) 'PKI direct-object write/delete/ACL audit only' Assert ($script:pkiRequest.Scope -eq 'OneLevel' -and $script:pkiRequest.Dn -eq "CN=Certificate Templates,CN=Public Key Services,CN=Services,$($session.ConfigurationDn)" -and $script:pkiRequest.Filter -eq '(objectGUID=\67\45\23\01\AB\89\EF\CD\01\23\45\67\89\AB\CD\EF)') 'PKI membership uses exact parent and byte-escaped object GUID' $script:pkiMember = $false $script:state.Dn = "CN=Test,$($session.DomainDn)" Assert ((@(Get-WelaAdSaclPlan $session @('PkiObjects') @($script:state.Dn)))[0].Status -eq 'Unknown') 'PKI class outside trusted forest containers refused' $script:state.Dn = "CN=Foo\,CN=Certificate Templates,CN=Public Key Services,CN=Services,$($session.ConfigurationDn)" Assert ((@(Get-WelaAdSaclPlan $session @('PkiObjects') @($script:state.Dn)))[0].Status -eq 'Unknown') 'escaped RDN suffix cannot impersonate approved PKI parent' Reset-Mocks; $session.Writable = $false Assert ((@(Get-WelaAdSaclPlan $session @('MdiDomain') @()))[0].Status -eq 'Blocked') 'RODC plan explicitly blocked' Reset-Mocks; $script:readError = $true $ctx = Get-Context; $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $script:writes -eq 0) 'access denied is failed/unknown, never an empty descriptor' Reset-Mocks; $ctx = Get-Context $true $report = Invoke-TestConfigure $ctx Assert ($report.DryRun -and $script:writes -eq 0 -and -not (Test-Path $ctx.BackupPath)) 'dry run does not write AD or journal' Reset-Mocks; $ctx = Get-Context $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 0 -and $report.Results[0].Status -eq 'Applied' -and $script:writes -eq 1) 'additive apply and final verification succeed' $journal = Get-Content (Join-Path $ctx.BackupPath 'before.jsonl') | ConvertFrom-Json Assert ($journal.Before.Descriptor.Sddl -like 'O:SY*' -and $journal.Before.ObjectGuid -eq $script:state.ObjectGuid) 'journal contains original SDDL and identity' $receipts = @(Get-ChildItem $ctx.BackupPath -Filter 'ad-sacl-*.json') $receipt = Get-Content $receipts[0].FullName -Raw | ConvertFrom-Json Assert ($receipt.AddedAces.Count -eq 6 -and $receipt.Before.Descriptor.Binary -eq 'before') 'receipt stores only intended additions and before binary' Assert ($receipt.ReceiptStatus -eq 'Confirmed' -and $receipt.ConfirmedAfter.Descriptor.Binary -eq 'after' -and $receipt.ConfirmedUtc) 'successful write and readback confirm rollback ownership' $ctx2 = Get-Context; $report2 = Invoke-TestConfigure $ctx2 Assert ($script:writes -eq 1 -and $report2.Results[0].Status -eq 'AlreadyCompliant') 'repeat run is idempotent' Reset-Mocks; $script:race = $true; $ctx = Get-Context $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $script:writes -eq 0 -and $report.Results[0].Diagnostic -like '*changed after journaling*') 'USN race fails closed before write' $pendingPath = @(Get-ChildItem $ctx.BackupPath -Filter 'ad-sacl-*.json')[0].FullName Assert ((Get-Content $pendingPath -Raw | ConvertFrom-Json).ReceiptStatus -eq 'Pending') 'stale pre-write receipt remains pending' $script:state.Descriptor.Sacl = @('unrelated') + @(Get-WelaAdAuditDefinitions | ForEach-Object { 'added-' + $_.Class }) $script:state.Descriptor.Binary = 'after' Assert-Throws { Invoke-WelaAdSaclRollback $session (Get-Context $true) $pendingPath } 'pending intent cannot authorize rollback of another writers matching ACEs' Reset-Mocks; $script:writeError = $true; $ctx = Get-Context $report = Invoke-TestConfigure $ctx $pendingPath = @(Get-ChildItem $ctx.BackupPath -Filter 'ad-sacl-*.json')[0].FullName Assert ($report.ExitCode -eq 1 -and (Get-Content $pendingPath -Raw | ConvertFrom-Json).ReceiptStatus -eq 'Pending') 'failed LDAP write cannot confirm receipt' Assert-Throws { Invoke-WelaAdSaclRollback $session (Get-Context $true) $pendingPath } 'failed-write receipt cannot authorize automatic rollback' Reset-Mocks; $script:badReadback = $true; $ctx = Get-Context Assert ((Invoke-TestConfigure $ctx).ExitCode -eq 1) 'missing audit ACE readback fails' $pendingPath = @(Get-ChildItem $ctx.BackupPath -Filter 'ad-sacl-*.json')[0].FullName Assert ((Get-Content $pendingPath -Raw | ConvertFrom-Json).ReceiptStatus -eq 'Pending') 'failed readback leaves receipt pending' Reset-Mocks; $script:removeExisting = $true; $ctx = Get-Context $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $report.Results[0].Diagnostic -like '*Existing owner*') 'loss of pre-existing audit ACE fails verification' Reset-Mocks; $script:finalDrift = $true; $ctx = Get-Context $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $report.Results[0].Status -eq 'Overridden') 'later missing WELA ACEs are overridden' Reset-Mocks; $ctx = Get-Context $false $false $script:onPrompt = { Remove-Item -LiteralPath $ctx.BackupPath -Recurse -Force } $report = Invoke-TestConfigure $ctx Assert ($report.ExitCode -eq 1 -and $script:writes -eq 0) 'journal failure prevents mutation' # Test schema and DC eligibility adapters with controlled directory responses. Set-Item function:Test-WelaAdDmsaDomain $script:originalDmsa function Search-WelaAdDirectory { param($Session, $Dn, $Filter, $Scope, $Attributes) [pscustomobject]@{ Dn = 'CN=DC1'; Values = @{ operatingSystemVersion = @($script:version) } } } $script:version = '10.0 (20348)'; Assert (-not (Test-WelaAdDmsaDomain $session)) 'Server 2022 is not dMSA eligible' $script:version = '10.0 (26100)'; Assert (Test-WelaAdDmsaDomain $session) 'Server 2025 version proves dMSA applicability' $script:version = 'unreadable'; Assert-Throws { Test-WelaAdDmsaDomain $session } 'unknown DC versions remain unknown' Set-Item function:Test-WelaAdSchemaClass $script:originalSchema function Search-WelaAdDirectory { param($Session, $Dn, $Filter, $Scope, $Attributes) [pscustomobject]@{ Dn = 'CN=User'; Values = @{ schemaIDGUID = @(,([guid]$script:schemaGuid).ToByteArray()) } } } $script:schemaGuid = $defs[0].InheritedObjectType Assert (Test-WelaAdSchemaClass $session user $script:schemaGuid) 'schema binary GUID checked' Assert-Throws { Test-WelaAdSchemaClass $session user $defs[1].InheritedObjectType } 'unexpected schema GUID rejected' # RootDSE binding validation with a fully fake connection: no platform/native calls. $savedOs = $env:OS try { $env:OS = 'Windows_NT'; $script:rootHost = 'dc1.example.test'; $script:rodc = 'FALSE'; $script:disposed = 0 function New-WelaAdConnection { param($Server) $fake = [pscustomobject]@{} $fake | Add-Member ScriptMethod Bind { } $fake | Add-Member ScriptMethod Dispose { $script:disposed++ } return $fake } function Search-WelaAdDirectory { param($Session, $Dn, $Filter, $Scope, $Attributes) if ($Dn -eq '') { [pscustomobject]@{ Dn = ''; Values = @{ dnsHostName = @($script:rootHost); defaultNamingContext = @('DC=example,DC=test'); configurationNamingContext = @('CN=Configuration,DC=example,DC=test'); schemaNamingContext = @('CN=Schema,CN=Configuration,DC=example,DC=test'); dsServiceName = @('CN=NTDS Settings,CN=DC1'); supportedCapabilities = @('1.2.840.113556.1.4.800'); supportedControl = @('1.2.840.113556.1.4.801') } } } else { [pscustomobject]@{ Dn = $Dn; Values = @{ 'msDS-isRODC' = @($script:rodc) } } } } $bound = Open-WelaAdSession 'dc1.example.test' Assert ($bound.Writable -and $bound.Server -eq 'dc1.example.test') 'RootDSE confirms exact selected writable DC' $script:rodc = 'TRUE'; Assert (-not (Open-WelaAdSession 'dc1.example.test').Writable) 'RootDSE RODC capability remains read-only' $script:rootHost = 'dc2.example.test' Assert-Throws { Open-WelaAdSession 'dc1.example.test' } 'wrong RootDSE host fails closed' Assert ($script:disposed -eq 1) 'failed binding validation disposes connection' Assert-Throws { Open-WelaAdSession 'LDAP://dc1.example.test' } 'LDAP URLs are not accepted as exact DC names' } finally { $env:OS = $savedOs } # No network is ever used. Native SID/ACL APIs are exercised in the Windows suite. $tokens = $null; $errors = $null [void][Management.Automation.Language.Parser]::ParseFile((Join-Path $repo 'WELA.ps1'), [ref]$tokens, [ref]$errors) Assert ($errors.Count -eq 0) 'WELA entry point parses' Write-Host "Passed $script:checks AD object SACL mocked assertions. No live AD connection or mutation occurred." } finally { Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue }