Public/Get-RBACGuard.ps1
|
function Get-RBACGuard { <# .SYNOPSIS Identifies the guardrails (gates / blocks) that could prohibit an action or a role assignment even when the required RBAC role is held. .DESCRIPTION Holding the required role is necessary but not sufficient. A caller can hold Contributor and still be blocked from creating a resource by an Azure Policy 'deny'; an executor can hold roleAssignments/write and still be blocked from granting a role by a deny assignment, a Conditional Access policy, a management lock, or a role that is only PIM-eligible (not active). Get-RBACGuard enumerates those guardrails for a platform at a scope and, with -Live, detects several of them against the live control plane: * Azure - management locks (Get-AzResourceLock), deny assignments (Get-AzDenyAssignment), policy assignments in scope (Get-AzPolicyAssignment), and PIM-eligible resource roles (Get-AzRoleEligibilitySchedule). Plus advisory network guards (Azure Firewall, NSG, resource firewall) and Defender-for-Cloud / Microsoft Sentinel awareness. * Graph - Conditional Access policies (Get-MgIdentityConditionalAccessPolicy) and PIM-eligible directory roles, plus advisory guards for restricted-management admin units, admin consent, authentication strength, and Defender for Identity. * Fabric - advisory guards for admin tenant settings, capacity state, and Conditional Access. * Purview - advisory guards for the metadata-policy gate, collection inheritance, and account network firewall. Every guard carries what it Affects (Action / RoleAssignment / Both), its Category (Preventive / Identity / Network / Detective), a Severity, a Status (Potential / Blocking / Clear / Detective), a Detail, a Mitigation, and a Microsoft Learn Reference. Detection is read-only and never changes state. Without -Live the result is the advisory knowledge base for the platform (every guard as 'Potential' / 'Detective'), which is fully offline and is how CI and the test suite exercise this command. .PARAMETER Platform Platform name or alias (Get-RBACProvider lists them). .PARAMETER Command Optional command / operation the guardrails are being assessed for; stamped onto each guard for correlation with a probe. .PARAMETER CallerId Optional identity whose eligible (PIM) roles and identity gates are inspected during -Live detection. .PARAMETER Scope Explicit scope. Overrides scope built from the Azure parameters below. .PARAMETER SubscriptionId / -ResourceGroupName / -ManagementGroupId / -ResourceId Parts used to build an ARM scope when -Scope is not supplied. .PARAMETER Live Detect guardrails against the live control plane (read-only). Azure and Graph support live detection; other platforms return the advisory set. .PARAMETER GuardType Optional filter: return only the named guard type(s), e.g. 'ResourceLock', 'DenyAssignment', 'ConditionalAccess', 'PrivilegedIdentityManagement'. .PARAMETER Affects Optional filter: 'Action', 'RoleAssignment', or 'Both'. Guards that affect 'Both' always match either filter. .PARAMETER Options Provider hashtable (reserved for future per-platform guard options). .PARAMETER MapPath Alternate guardrail knowledge-base path (testing). .PARAMETER TenantId / -RunAs* Probe-identity context (see Get-RBACRequirement). .EXAMPLE Get-RBACGuard -Platform Azure -SubscriptionId SUB1 -ResourceGroupName rg1 Lists the advisory guardrails that could block an operation or a role grant in that resource group. .EXAMPLE Get-RBACGuard -Platform Azure -Scope /subscriptions/SUB1/resourceGroups/rg1 ` -CallerId dev@contoso.com -Live | Where-Object Status -eq 'Blocking' Detects live blockers - locks, deny assignments - at the scope. .EXAMPLE Get-RBACGuard -Platform 'Microsoft Graph' -Affects RoleAssignment Shows only the Entra guardrails that could block *granting* a role (Conditional Access, PIM, restricted-management admin units). .OUTPUTS PSCustomObject (PSAutoRBAC.Guard) per detected / applicable guardrail. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Platform, [Parameter()] [string]$Command, [Parameter()] [string]$CallerId, [Parameter()] [string]$Scope, [Parameter()] [string]$SubscriptionId, [Parameter()] [string]$ResourceGroupName, [Parameter()] [string]$ManagementGroupId, [Parameter()] [string]$ResourceId, [Parameter()] [switch]$Live, [Parameter()] [string[]]$GuardType, [Parameter()] [ValidateSet('Action', 'RoleAssignment', 'Both')] [string]$Affects, [Parameter()] [hashtable]$Options, [Parameter()] [string]$MapPath, [Parameter()] [string]$TenantId, [Parameter()] [pscredential]$RunAsCredential, [Parameter()] [pscredential]$RunAsServicePrincipal, [Parameter()] [string]$RunAsTenantId, [Parameter()] [switch]$RunAsManagedIdentity, [Parameter()] [string]$RunAsManagedIdentityClientId ) $provider = Get-RBACProviderInternal -Platform $Platform $context = Initialize-RBACContext -BoundParameters $PSBoundParameters Write-PSFMessage -Level Verbose -Message "Get-RBACGuard: platform '$($provider.Name)', command '$Command', caller '$CallerId', live=$Live." -Tag 'PSAutoRBAC', 'Public', 'Guard' try { $resolvedScope = Resolve-RBACScope -Scope $Scope -SubscriptionId $SubscriptionId ` -ResourceGroupName $ResourceGroupName -ManagementGroupId $ManagementGroupId ` -ResourceId $ResourceId -AllowTenantRoot $opts = @{} if ($Options) { $opts = $Options.Clone() } if ($Live) { $opts['Live'] = $true } if ($PSBoundParameters.ContainsKey('GuardType')) { $opts['GuardType'] = $GuardType } if ($PSBoundParameters.ContainsKey('Affects')) { $opts['Affects'] = $Affects } if ($MapPath) { $opts['MapPath'] = $MapPath } if ($Live -and -not ($provider.PSObject.Properties.Name -contains 'SupportsGuardProbe' -and $provider.SupportsGuardProbe)) { Write-PSFMessage -Level Verbose -Message "Get-RBACGuard: platform '$($provider.Name)' has no live guard detection; returning advisory guardrails." -Tag 'PSAutoRBAC', 'Public', 'Guard' } $guards = @(Invoke-RBACProviderGuard -Provider $provider -Command $Command -CallerId $CallerId -Scope $resolvedScope -Context $context -Options $opts) $blocking = @($guards | Where-Object { $_.Status -eq 'Blocking' }).Count Write-PSFMessage -Level Verbose -Message "Get-RBACGuard: $($guards.Count) guardrail(s) for '$($provider.Name)' ($blocking blocking)." -Tag 'PSAutoRBAC', 'Public', 'Guard' $guards } finally { if ($context.IsRunAs) { & $context.Disconnect } } } |