Public/Invoke-RBACProbe.ps1
|
function Invoke-RBACProbe { <# .SYNOPSIS Probes the RBAC a command needs and whether a caller already has it. .DESCRIPTION The flagship PSAutoRBAC operation. For a platform command it: 1. Resolves the least-privilege requirement (provider preflight). 2. Optionally derives the requirement from a *live* execution when -LiveProbe is supplied - only the Azure provider can do this, because only ARM names the missing action in its AuthorizationFailed error. Live probing is gated by ShouldProcess: although it prefers the command's own -WhatIf, it may reach the service and is therefore treated as a state-changing action. 3. Tests whether the target -CallerId holds each required role at the scope (non-destructive). 4. Emits, per required role, a combined result carrying the requirement, the access verdict, and idempotent grant/revoke snippets. Preflight (the default) makes no changes on any platform. .PARAMETER Platform Platform name or alias (Get-RBACProvider lists them). .PARAMETER Command The command / operation being probed. .PARAMETER CallerId The identity whose access is evaluated. .PARAMETER Scope Explicit scope. Overrides scope built from the Azure parameters below. .PARAMETER SubscriptionId Azure subscription id used to build an ARM scope. .PARAMETER ResourceGroupName Azure resource group used to build an ARM scope. .PARAMETER ManagementGroupId Azure management group used to build an ARM scope. .PARAMETER ResourceId Explicit ARM resource id scope. .PARAMETER ArgumentList Arguments passed to the command during -LiveProbe (and recorded on output). .PARAMETER LiveProbe Actually execute the command to derive the requirement from the live authorization failure. Azure only; ShouldProcess-gated. .PARAMETER RoleAssignment Pre-fetched assignments/roles for offline access evaluation. .PARAMETER Options Provider hashtable (e.g. @{ WorkspaceId='...'; Collection='...' }). .PARAMETER MapPath Alternate knowledge-base path (testing). .PARAMETER TenantId Optional tenant id. .PARAMETER RunAsCredential Probe as a user credential rather than the ambient session. .PARAMETER RunAsServicePrincipal Probe as a service principal (with -RunAsTenantId). .PARAMETER RunAsTenantId Tenant for service-principal run-as. .PARAMETER RunAsManagedIdentity Probe as a managed identity. .PARAMETER RunAsManagedIdentityClientId Client id for a user-assigned managed identity. .EXAMPLE Invoke-RBACProbe -Platform Azure -Command New-AzResourceGroup ` -CallerId a@b.com -SubscriptionId SUB1 Preflight: resolves Contributor, checks whether a@b.com holds it. .EXAMPLE Invoke-RBACProbe -Platform Azure -Command { New-AzStorageAccount ... } ` -CallerId a@b.com -Scope /subscriptions/SUB1/resourceGroups/rg -LiveProbe Live: runs the command, parses the AuthorizationFailed, maps the action to a role. .OUTPUTS PSCustomObject (PSAutoRBAC.ProbeResult) per required role. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Platform, [Parameter(Mandatory)] [ValidateNotNull()] [object]$Command, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$CallerId, [Parameter()] [string]$Scope, [Parameter()] [string]$SubscriptionId, [Parameter()] [string]$ResourceGroupName, [Parameter()] [string]$ManagementGroupId, [Parameter()] [string]$ResourceId, [Parameter()] [Alias('Args')] [object[]]$ArgumentList, [Parameter()] [switch]$LiveProbe, [Parameter()] [object[]]$RoleAssignment, [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 # A scriptblock command has no name; use a label for output/messages. $commandName = if ($Command -is [scriptblock]) { '<scriptblock>' } else { [string]$Command } Write-PSFMessage -Level Verbose -Message "Invoke-RBACProbe: platform '$($provider.Name)', command '$commandName', caller '$CallerId', live=$LiveProbe." -Tag 'PSAutoRBAC', 'Public', 'Probe' try { $resolvedScope = Resolve-RBACScope -Scope $Scope -SubscriptionId $SubscriptionId ` -ResourceGroupName $ResourceGroupName -ManagementGroupId $ManagementGroupId ` -ResourceId $ResourceId -AllowTenantRoot Write-PSFMessage -Level Debug -Message "Invoke-RBACProbe: resolved scope '$resolvedScope'." -Tag 'PSAutoRBAC', 'Public', 'Probe' $opts = @{} if ($Options) { $opts = $Options.Clone() } if ($MapPath) { $opts['MapPath'] = $MapPath } # 1/2. Requirement - preflight, or live for Azure. $mode = 'Preflight' if ($LiveProbe) { if (-not $provider.SupportsLiveProbe) { Write-PSFMessage -Level Warning -Message "Platform '$($provider.Name)' does not support live probing (its authorization error names no permission). Falling back to preflight resolution." -Tag 'PSAutoRBAC', 'Public', 'Probe' $requirement = & $provider.ResolveRequirement $commandName $context $opts } elseif ($PSCmdlet.ShouldProcess("$commandName (as probe identity)", 'Execute command to derive RBAC requirement (LiveProbe)')) { Write-PSFMessage -Level Significant -Message "Invoke-RBACProbe: executing live probe of '$commandName'." -Tag 'PSAutoRBAC', 'Public', 'Probe', 'LiveProbe' $requirement = & $provider.ProbeLive $Command $ArgumentList $resolvedScope $context $mode = 'LiveProbe' } else { Write-PSFMessage -Level Verbose -Message 'Invoke-RBACProbe: live probe declined via ShouldProcess; using preflight.' -Tag 'PSAutoRBAC', 'Public', 'Probe' $requirement = & $provider.ResolveRequirement $commandName $context $opts } } else { $requirement = & $provider.ResolveRequirement $commandName $context $opts } # 3. Access verdict. $roles = @($requirement.Roles | Where-Object { $_ }) $testOpts = $opts.Clone() if ($PSBoundParameters.ContainsKey('RoleAssignment')) { $testOpts['RoleAssignment'] = $RoleAssignment } $states = @() if ($roles.Count -gt 0) { $states = @(& $provider.TestAccess $CallerId $roles $resolvedScope $context $testOpts) } # 4. Per-role combined result. if ($states.Count -eq 0) { [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.ProbeResult' Platform = $provider.Name Command = $commandName CallerId = $CallerId Scope = $resolvedScope Role = $null HasAccess = $null Mode = $mode IsKnown = $requirement.IsKnown Source = $requirement.Source Permissions = $requirement.Permissions Notes = $requirement.Notes AddScript = $null RemoveScript = $null } return } foreach ($state in $states) { $scripts = & $provider.NewGrantScript $CallerId $state.Role $resolvedScope $opts [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.ProbeResult' Platform = $provider.Name Command = $commandName CallerId = $CallerId Scope = $resolvedScope Role = $state.Role HasAccess = $state.HasAccess Mode = $mode IsKnown = $requirement.IsKnown Source = $requirement.Source Permissions = $requirement.Permissions Notes = $requirement.Notes AddScript = $scripts.AddScript RemoveScript = $scripts.RemoveScript } } } finally { if ($context.IsRunAs) { & $context.Disconnect } } } |