Private/Get-RBACGuardDefinition.ps1
|
function New-RBACGuardObject { <# .SYNOPSIS Builds a normalized PSAutoRBAC.Guard object. .DESCRIPTION Internal factory shared by Get-RBACGuardDefinition (advisory, offline) and the provider ResolveGuard scriptblocks (live detection). Centralizing the shape keeps every guard - whether surfaced from the knowledge base or from a live control - identical, so callers can filter and format them uniformly. .OUTPUTS PSCustomObject (PSAutoRBAC.Guard) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Factory; constructs and returns a guard object, makes no state change.')] [OutputType([psobject])] param( [Parameter(Mandatory)] [string]$Platform, [Parameter(Mandatory)] [string]$GuardType, [Parameter()] [string]$Command, [Parameter()] [string]$CallerId, [Parameter()] [string]$Scope, [Parameter()] [ValidateSet('Action', 'RoleAssignment', 'Both')] [string]$Affects = 'Both', [Parameter()] [ValidateSet('Preventive', 'Identity', 'Network', 'Detective')] [string]$Category = 'Preventive', [Parameter()] [ValidateSet('High', 'Medium', 'Info')] [string]$Severity = 'Medium', [Parameter()] [ValidateSet('Blocking', 'Potential', 'Clear', 'Detective', 'Unknown')] [string]$Status = 'Potential', [Parameter()] [string]$Detail, [Parameter()] [string]$Mitigation, [Parameter()] [string]$Reference, [Parameter()] [ValidateSet('KnowledgeBase', 'Live')] [string]$Source = 'KnowledgeBase', [Parameter()] [object[]]$Evidence ) [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Guard' Platform = $Platform Command = $Command CallerId = $CallerId Scope = $Scope GuardType = $GuardType Affects = $Affects Category = $Category Severity = $Severity Status = $Status Detail = $Detail Mitigation = $Mitigation Reference = $Reference Source = $Source Evidence = @($Evidence) } } function Get-RBACGuardDefinition { <# .SYNOPSIS Returns the advisory guardrail definitions for a platform from the knowledge base. .DESCRIPTION Internal. Reads Data/GuardrailMap.psd1 and projects each guard definition for the requested platform into a PSAutoRBAC.Guard object with Status = 'Potential' (a gate that *may* apply) or 'Detective' (a control that watches rather than blocks). Provider ResolveGuard scriptblocks call this for their offline baseline, then optionally promote individual guards to 'Blocking'/'Clear' from live detection. This is the pure-data layer: every guard the framework knows about is declared in GuardrailMap.psd1, so extending coverage is a data edit, not a code change - mirroring the CommandRoleMap knowledge base. .PARAMETER Platform Canonical platform name (e.g. 'Azure', 'Microsoft Graph'). .PARAMETER GuardType Optional filter: return only the named guard type(s). .PARAMETER Affects Optional filter: 'Action', 'RoleAssignment', or 'Both'. 'Both' entries always match either filter because they affect both paths. .PARAMETER Command / -CallerId / -Scope Context stamped onto each returned guard. .PARAMETER MapPath Optional explicit GuardrailMap path (testing). .OUTPUTS PSCustomObject (PSAutoRBAC.Guard) #> [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory)] [string]$Platform, [Parameter()] [string[]]$GuardType, [Parameter()] [ValidateSet('Action', 'RoleAssignment', 'Both')] [string]$Affects, [Parameter()] [string]$Command, [Parameter()] [string]$CallerId, [Parameter()] [string]$Scope, [Parameter()] [string]$MapPath ) $map = if ($MapPath) { Get-RBACKnowledgeBase -Path $MapPath } else { Get-RBACKnowledgeBase -Name 'GuardrailMap' } $platformKey = $map.Keys | Where-Object { $_ -eq $Platform } | Select-Object -First 1 if (-not $platformKey) { Write-PSFMessage -Level Debug -Message "Guardrail map has no entry for platform '$Platform'." -Tag 'PSAutoRBAC', 'Guard' return } $guards = $map[$platformKey] foreach ($type in ($guards.Keys | Sort-Object)) { if ($GuardType -and ($GuardType -notcontains $type)) { continue } $def = $guards[$type] # 'Both'-affecting guards are relevant to either an Action or a RoleAssignment filter. if ($Affects -and $def.Affects -ne 'Both' -and $def.Affects -ne $Affects) { continue } $status = if ($def.Category -eq 'Detective') { 'Detective' } else { 'Potential' } New-RBACGuardObject -Platform $Platform -GuardType $type ` -Command $Command -CallerId $CallerId -Scope $Scope ` -Affects $def.Affects -Category $def.Category -Severity $def.Severity ` -Status $status -Detail $def.Detail -Mitigation $def.Mitigation ` -Reference $def.Reference -Source 'KnowledgeBase' } } function Invoke-RBACProviderGuard { <# .SYNOPSIS Dispatches guardrail evaluation to a provider (or the advisory fallback). .DESCRIPTION Internal. If the provider implements the optional ResolveGuard member, it is invoked and owns both the offline advisory baseline and any live detection. Providers that do not implement it still yield the knowledge-base advisory guards via Get-RBACGuardDefinition, so guard analysis degrades gracefully for third-party providers. Returned guards are sorted by severity (High -> Medium -> Info) for stable, human-friendly output. .OUTPUTS PSCustomObject (PSAutoRBAC.Guard) #> [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory)] [psobject]$Provider, [Parameter()] [string]$Command, [Parameter()] [string]$CallerId, [Parameter()] [string]$Scope, [Parameter()] [psobject]$Context, [Parameter()] [hashtable]$Options ) $opts = @{} if ($Options) { $opts = $Options.Clone() } $guards = @() if ($Provider.PSObject.Properties.Name -contains 'ResolveGuard' -and $Provider.ResolveGuard) { $isLive = [bool]($opts.ContainsKey('Live') -and $opts['Live']) Write-PSFMessage -Level Debug -Message "Guard: dispatching to '$($Provider.Name)' ResolveGuard (live=$isLive)." -Tag 'PSAutoRBAC', 'Guard' $guards = @(& $Provider.ResolveGuard $Command $CallerId $Scope $Context $opts) } else { Write-PSFMessage -Level Debug -Message "Guard: '$($Provider.Name)' has no ResolveGuard; using advisory knowledge base." -Tag 'PSAutoRBAC', 'Guard' $defParams = @{ Platform = $Provider.Name; Command = $Command; CallerId = $CallerId; Scope = $Scope } if ($opts.ContainsKey('GuardType') -and $opts['GuardType']) { $defParams['GuardType'] = $opts['GuardType'] } if ($opts.ContainsKey('Affects') -and $opts['Affects']) { $defParams['Affects'] = $opts['Affects'] } if ($opts.ContainsKey('MapPath') -and $opts['MapPath']) { $defParams['MapPath'] = $opts['MapPath'] } $guards = @(Get-RBACGuardDefinition @defParams) } $severityRank = @{ 'High' = 0; 'Medium' = 1; 'Info' = 2 } $guards | Sort-Object -Property @{ Expression = { $severityRank[$_.Severity] } }, GuardType } |