From 76ec67f05280e4d4e0a92a4e7414ddb0fef3bc2b Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Mon, 20 Apr 2026 10:57:09 -0600 Subject: [PATCH] feat: add LAPS reader permission verification to validator **Added:** - Implemented check to verify that configured LAPS reader accounts/groups have permission to read the ms-Mcs-AdmPwd attribute on computer objects in each domain. The validator now reports PASS/FAIL/WARN for each reader account based on detected permissions. --- cli/internal/validate/checks.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cli/internal/validate/checks.go b/cli/internal/validate/checks.go index 60a5c4f8..7aabcfcb 100644 --- a/cli/internal/validate/checks.go +++ b/cli/internal/validate/checks.go @@ -698,6 +698,35 @@ func (v *Validator) checkLAPS(ctx context.Context, w io.Writer) { v.addResult(w, "FAIL", "LAPS", fmt.Sprintf("LAPS password NOT set for %s", hostname), "") } } + + // Verify LAPS reader permissions — ensure configured accounts/groups + // can actually read the ms-Mcs-AdmPwd attribute on computer objects. + readerFacts := v.lab.DomainsWithLAPSReaders() + for _, lf := range readerFacts { + dc := strings.ToUpper(lf.DCRole) + if !v.hasHost(dc) { + continue + } + for _, reader := range lf.Readers { + output := v.runPS(ctx, dc, fmt.Sprintf( + `$computers = Get-ADComputer -Filter {ms-Mcs-AdmPwd -like '*'} -Properties ms-Mcs-AdmPwd -SearchBase (Get-ADDomain).DistinguishedName -ErrorAction SilentlyContinue; `+ + `Import-Module ActiveDirectory; Set-Location AD:; `+ + `$found = $false; foreach ($c in $computers) { `+ + `$acl = Get-Acl -Path $c.DistinguishedName; `+ + `$match = $acl.Access | Where-Object { $_.IdentityReference -like '*%s*' }; `+ + `if ($match) { $found = $true; break } }; `+ + `if ($found) { Write-Output 'READER_OK' } else { Write-Output 'READER_NOT_FOUND' }`, + reader)) + switch { + case strings.Contains(output, "READER_OK"): + v.addResult(w, "PASS", "LAPS", fmt.Sprintf("%s has LAPS read permission in %s", reader, lf.Domain), "") + case strings.Contains(output, "READER_NOT_FOUND"): + v.addResult(w, "FAIL", "LAPS", fmt.Sprintf("%s does NOT have LAPS read permission in %s", reader, lf.Domain), "") + default: + v.addResult(w, "WARN", "LAPS", fmt.Sprintf("Could not verify LAPS reader %s in %s", reader, lf.Domain), "") + } + } + } } func (v *Validator) checkSIDFiltering(ctx context.Context, w io.Writer) {