Private/Get-EscCaRoleAssignment.ps1
|
function Get-EscCaRoleAssignment { <# .SYNOPSIS Summarizes CA management-role holders (Manage CA / Issue and Manage Certificates) per CA and flags any assignment outside the default groups. .DESCRIPTION Reads the parsed CA security descriptor (Context.CaConfigs[].SecurityAces) and, for every Allow ACE that grants ManageCA (CA Administrator) and/or ManageCertificates (Certificate Manager / "Issue and Manage Certificates"), emits one row per principal. Each row is marked IsDefault when the principal is one of the well-known privileged holders present on a fresh Enterprise CA: - BUILTIN\Administrators (S-1-5-32-544) - Domain Admins (S-1-5-21-<domain>-512) - Enterprise Admins (S-1-5-21-<domain>-519) - Local SYSTEM (S-1-5-18) [benign machine principal] Anything else holding a management right is a non-default assignment and is surfaced for review. This is presentation/inventory data; the ESC7 analyzer still independently flags the low-privileged subset as Vulnerable. Read-only: consumes already-collected ACEs, performs no directory or CA I/O. .PARAMETER CaConfig CA descriptor objects (Name, DnsHostName, Reachable, SecurityAces[]). .PARAMETER ExtraDefaultSid Additional SID strings to treat as default/expected (e.g. an environment's sanctioned Tier-0 PKI admin group) so they are not flagged as non-default. .OUTPUTS [pscustomobject] per CA: Name, DnsHostName, Reachable, AcesAvailable(bool), NonDefaultCount(int), Roles(object[]) where each role is { Principal, Sid, ManageCA(bool), ManageCertificates(bool), IsDefault(bool), IsLowPriv(bool) }. #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]] $CaConfig = @(), [Parameter(Mandatory = $false)] [string[]] $ExtraDefaultSid = @() ) # Well-known default holders of CA management rights on a fresh Enterprise CA. $extra = @($ExtraDefaultSid | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim().ToUpperInvariant() }) $isDefaultSid = { param($Sid) if ([string]::IsNullOrWhiteSpace($Sid)) { return $false } $s = $Sid.Trim().ToUpperInvariant() if ($s -eq 'S-1-5-32-544') { return $true } # BUILTIN\Administrators if ($s -eq 'S-1-5-18') { return $true } # Local SYSTEM (benign) # Domain Admins (RID 512) / Enterprise Admins (RID 519) under any domain. if ($s -match '^S-1-5-21-[0-9-]+-(512|519)$') { return $true } if ($extra -contains $s) { return $true } return $false } $out = @() foreach ($ca in @($CaConfig)) { if ($null -eq $ca) { continue } $reachable = [bool]$ca.Reachable $aces = $null if ($ca.PSObject.Properties.Match('SecurityAces').Count -gt 0) { $aces = $ca.SecurityAces } $acesAvailable = ($null -ne $aces) $roles = @() if ($acesAvailable) { # Collapse per principal: a SID may appear in multiple ACEs. $byPrincipal = [ordered]@{} foreach ($ace in @($aces)) { if ($null -eq $ace) { continue } if ($ace.AceType -ne 'Allow') { continue } $hasCa = (@($ace.Rights) -contains 'ManageCA') $hasCert = (@($ace.Rights) -contains 'ManageCertificates') if (-not ($hasCa -or $hasCert)) { continue } $sid = [string]$ace.PrincipalSid $key = $sid if ([string]::IsNullOrWhiteSpace($key)) { $key = [string]$ace.PrincipalName } if ([string]::IsNullOrWhiteSpace($key)) { continue } if (-not $byPrincipal.Contains($key)) { $byPrincipal[$key] = [pscustomobject]@{ Principal = [string]$ace.PrincipalName Sid = $sid ManageCA = $false ManageCertificates = $false IsDefault = (& $isDefaultSid $sid) IsLowPriv = [bool]$ace.IsLowPriv } } $row = $byPrincipal[$key] if ($hasCa) { $row.ManageCA = $true } if ($hasCert) { $row.ManageCertificates = $true } if ($ace.IsLowPriv) { $row.IsLowPriv = $true } } # Non-default first, then low-priv, then name - most interesting on top. $roles = @($byPrincipal.Values | Sort-Object ` @{ Expression = { -not $_.IsDefault }; Descending = $true }, ` @{ Expression = { [bool]$_.IsLowPriv }; Descending = $true }, ` @{ Expression = { if ($_.Principal) { $_.Principal } else { $_.Sid } }; Descending = $false }) } $nonDefault = @($roles | Where-Object { -not $_.IsDefault }).Count $out += [pscustomobject]@{ Name = [string]$ca.Name DnsHostName = [string]$ca.DnsHostName Reachable = $reachable AcesAvailable = $acesAvailable NonDefaultCount = [int]$nonDefault Roles = @($roles) } } return $out } |