Public/Set-RBACAccess.ps1
|
function Set-RBACAccess { <# .SYNOPSIS Discovers, evaluates, and (optionally) grants the least-privilege RBAC a caller needs to run a command. .DESCRIPTION End-to-end orchestration over the probe providers: 1. Resolve the requirement (preflight, or -LiveProbe for Azure). 2. Test whether -CallerId already holds each required role at the scope. 3. Generate idempotent grant / revoke snippets. 4. With -WhatIf, only report. Otherwise grant missing roles, honouring -Confirm. Automatic granting is implemented for Azure (New-AzRoleAssignment); for Graph / Fabric / Purview the AddScript is returned for review and the result is marked Applied = $false, because those grants are intentionally not executed automatically. Designed for Zero Trust provisioning: a framework identity holding only the relevant *roleAssignments write* permission grants the caller exactly the roles an operation needs - and the RemoveScript revokes them after. .PARAMETER Platform Platform name or alias. .PARAMETER Command The command whose requirement is evaluated. .PARAMETER CallerId The identity to evaluate and grant. .PARAMETER Scope / -SubscriptionId / -ResourceGroupName / -ManagementGroupId / -ResourceId Scope, or the parts to build an ARM scope from. .PARAMETER ArgumentList Arguments for -LiveProbe (and recorded on output). .PARAMETER LiveProbe Derive the requirement from a live Azure failure (Azure only). .PARAMETER RoleAssignment Pre-fetched assignments/roles for offline evaluation. .PARAMETER Options Provider hashtable (e.g. @{ WorkspaceId='...' }). .PARAMETER MapPath Alternate knowledge-base path (testing). .PARAMETER TenantId / -RunAs* Probe-identity context (see Get-RBACRequirement). .EXAMPLE Set-RBACAccess -Platform Azure -SubscriptionId SUB1 -CallerId a@b.com ` -Command New-AzResourceGroup -WhatIf .OUTPUTS PSCustomObject (PSAutoRBAC.AccessResult) per required role. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [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 $commandName = if ($Command -is [scriptblock]) { '<scriptblock>' } else { [string]$Command } Write-PSFMessage -Level Verbose -Message "Set-RBACAccess: platform '$($provider.Name)', command '$commandName', caller '$CallerId'." -Tag 'PSAutoRBAC', 'Public', 'Access' try { $resolvedScope = Resolve-RBACScope -Scope $Scope -SubscriptionId $SubscriptionId ` -ResourceGroupName $ResourceGroupName -ManagementGroupId $ManagementGroupId ` -ResourceId $ResourceId -AllowTenantRoot $opts = @{} if ($Options) { $opts = $Options.Clone() } if ($MapPath) { $opts['MapPath'] = $MapPath } if ($LiveProbe -and $provider.SupportsLiveProbe -and $PSCmdlet.ShouldProcess("$commandName (as probe identity)", 'Execute command to derive RBAC requirement (LiveProbe)')) { $requirement = & $provider.ProbeLive $Command $ArgumentList $resolvedScope $context } else { $requirement = & $provider.ResolveRequirement $commandName $context $opts } $roles = @($requirement.Roles | Where-Object { $_ }) $testOpts = $opts.Clone() if ($PSBoundParameters.ContainsKey('RoleAssignment')) { $testOpts['RoleAssignment'] = $RoleAssignment } $states = if ($roles.Count) { @(& $provider.TestAccess $CallerId $roles $resolvedScope $context $testOpts) } else { @() } foreach ($state in $states) { $scripts = & $provider.NewGrantScript $CallerId $state.Role $resolvedScope $opts $applied = $false if ($state.HasAccess -ne $true) { Write-PSFMessage -Level Verbose -Message "Set-RBACAccess: '$CallerId' is missing '$($state.Role)' at '$resolvedScope'." -Tag 'PSAutoRBAC', 'Public', 'Access' if ($PSCmdlet.ShouldProcess("$CallerId at $resolvedScope", "Grant '$($state.Role)' on $($provider.Name)")) { if ($provider.Name -eq 'Azure' -and (Get-Command New-AzRoleAssignment -ErrorAction SilentlyContinue)) { $azp = @{ SignInName = $CallerId; RoleDefinitionName = $state.Role; Scope = $resolvedScope; ErrorAction = 'Stop' } if ($context.AzContext) { $azp['DefaultProfile'] = $context.AzContext } New-AzRoleAssignment @azp | Out-Null $applied = $true Write-PSFMessage -Level Significant -Message "Set-RBACAccess: granted '$($state.Role)' to '$CallerId' at '$resolvedScope'." -Tag 'PSAutoRBAC', 'Public', 'Access' } else { Write-PSFMessage -Level Warning -Message "Automatic grant is not performed for '$($provider.Name)'. Run the AddScript on the result from an identity with the necessary management permission." -Tag 'PSAutoRBAC', 'Public', 'Access' } } } else { Write-PSFMessage -Level Debug -Message "Set-RBACAccess: '$CallerId' already holds '$($state.Role)'; nothing to do." -Tag 'PSAutoRBAC', 'Public', 'Access' } [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.AccessResult' Platform = $provider.Name Command = $commandName CallerId = $CallerId Scope = $resolvedScope Role = $state.Role HasAccess = $state.HasAccess Applied = $applied IsKnown = $requirement.IsKnown Source = $requirement.Source Permissions = $requirement.Permissions Notes = $requirement.Notes AddScript = $scripts.AddScript RemoveScript = $scripts.RemoveScript } } } finally { if ($context.IsRunAs) { & $context.Disconnect } } } |