Providers/PurviewProbeProvider.ps1
|
function New-PurviewProbeProvider { <# .SYNOPSIS Constructs the Microsoft Purview RBAC probe provider. .DESCRIPTION Internal factory. Purview governs data-plane access through metadata policies (collection-scoped roles such as Collection Admin, Data Source Admin, Data Curator, Data Reader), not the Azure action model, and its 403s carry no role detail. This provider resolves requirements from the knowledge base and evaluates access (best effort) by reading the account's metadata policies via the Purview policystore REST API. Supply the account data-plane endpoint in -Scope (https://{account}.purview.azure.com) and, optionally, a collection name in -Options @{ Collection = '...' }. .OUTPUTS PSCustomObject (PSAutoRBAC.Provider) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Factory; constructs and returns a provider object, makes no state change.')] [OutputType([psobject])] param() $resourceUrl = 'https://purview.azure.net' [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Provider' Name = 'Microsoft Purview' Aliases = @('Purview', 'Atlas') SupportsLiveProbe = $false MatchScope = { param($Assignment, $Scope) $true } ResolveRequirement = { param($Command, $Context, $Options) Write-PSFMessage -Level Verbose -Message "Purview: resolving requirement for '$Command'." -Tag 'PSAutoRBAC', 'Provider', 'Purview' $mapPath = if ($Options -and $Options.ContainsKey('MapPath')) { $Options.MapPath } else { $null } $map = if ($mapPath) { Get-RBACKnowledgeBase -Path $mapPath } else { Get-RBACKnowledgeBase -Name 'CommandRoleMap' } $commands = $map['Microsoft Purview'] $commandKey = $commands.Keys | Where-Object { $_ -eq $Command } | Select-Object -First 1 $isKnown = [bool]$commandKey if (-not $commandKey) { $commandKey = '*Default' } $entry = $commands[$commandKey] [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Requirement' Platform = 'Microsoft Purview' Command = $Command Roles = @($entry.Roles) Permissions = @($entry.Actions) ScopeLevel = $entry.ScopeLevel Notes = $entry.Notes IsKnown = $isKnown Source = 'KnowledgeBase' } } TestAccess = { param($CallerId, $RequiredRole, $Scope, $Context, $Options) $resourceUrl = 'https://purview.azure.net' Write-PSFMessage -Level Verbose -Message "Purview: testing '$CallerId' for [$(@($RequiredRole) -join ', ')] at '$Scope'." -Tag 'PSAutoRBAC', 'Provider', 'Purview' $heldRoles = @() $evaluated = $true if ($Options -and $Options.ContainsKey('RoleAssignment')) { $heldRoles = @($Options.RoleAssignment | ForEach-Object { if ($_ -is [string]) { $_ } elseif ($_.Role) { $_.Role } elseif ($_.RoleDefinitionName) { $_.RoleDefinitionName } }) Write-PSFMessage -Level Debug -Message "Purview: evaluating against supplied role(s) [$($heldRoles -join ', ')] (offline)." -Tag 'PSAutoRBAC', 'Provider', 'Purview' } elseif ($Scope -and $Scope -match '^https?://') { $endpoint = $Scope.TrimEnd('/') $uri = "$endpoint/policystore/metadataPolicies?api-version=2021-07-01" Write-PSFMessage -Level Debug -Message "Purview: reading metadata policies at '$endpoint'." -Tag 'PSAutoRBAC', 'Provider', 'Purview' $res = Invoke-RBACRestRequest -Context $Context -ResourceUrl $resourceUrl -Uri $uri if ($res.Success) { # Walk decision rules; collect roles whose attribute rules reference the caller. foreach ($policy in @($res.Content.values)) { foreach ($rule in @($policy.properties.attributeRules)) { $refsCaller = ($rule | ConvertTo-Json -Depth 30) -match [regex]::Escape($CallerId) if ($refsCaller -and $rule.id -match ':role:(?<r>[^:]+)') { $heldRoles += $Matches['r'] } } } Write-PSFMessage -Level Debug -Message "Purview: '$CallerId' holds role(s) [$($heldRoles -join ', ')]." -Tag 'PSAutoRBAC', 'Provider', 'Purview' } elseif ($res.IsAuthorizationError) { Write-PSFMessage -Level Verbose -Message "Purview: authorization error reading metadata policies at '$endpoint'; treating as no access." -Tag 'PSAutoRBAC', 'Provider', 'Purview' $heldRoles = @() } else { $evaluated = $false; Write-PSFMessage -Level Warning -Message "Purview: could not read metadata policies at '$endpoint': $($res.Message)" -Tag 'PSAutoRBAC', 'Provider', 'Purview' } } else { $evaluated = $false Write-PSFMessage -Level Warning -Message 'Purview: access evaluation needs the account endpoint in -Scope (https://{account}.purview.azure.com), or -RoleAssignment for offline checks.' -Tag 'PSAutoRBAC', 'Provider', 'Purview' } foreach ($role in $RequiredRole) { $has = if (-not $evaluated) { $null } else { [bool](@($heldRoles) -contains $role) } [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.AccessState' Platform = 'Microsoft Purview' CallerId = $CallerId Role = $role Scope = $Scope HasAccess = $has } } } ProbeLive = { param($Command, $ArgumentList, $Scope, $Context) $map = Get-RBACKnowledgeBase -Name 'CommandRoleMap' $commands = $map['Microsoft Purview'] $key = $commands.Keys | Where-Object { $_ -eq $Command } | Select-Object -First 1 if (-not $key) { $key = '*Default' } $entry = $commands[$key] [pscustomobject]@{ PSTypeName = 'PSAutoRBAC.Requirement' Platform = 'Microsoft Purview'; Command = $Command Roles = @($entry.Roles); Permissions = @($entry.Actions); ScopeLevel = $entry.ScopeLevel Notes = 'Purview 403s do not name the required role; requirement resolved from the knowledge base, not a live failure.' IsKnown = [bool]$key; Source = 'KnowledgeBase' } } NewGrantScript = { param($CallerId, $Role, $Scope, $Options) $endpoint = if ($Options -and $Options.ContainsKey('Endpoint')) { $Options.Endpoint } else { $Scope } $collection = if ($Options -and $Options.ContainsKey('Collection')) { $Options.Collection } else { '<rootCollectionName>' } $add = @" # PSAutoRBAC: grant Purview data-plane role '$Role' to '$CallerId' on collection '$collection'. # Purview roles are granted by editing the collection's METADATA POLICY (policystore API), # not by a simple role-assignment call. Run as a Collection Admin. # 1) GET the metadata policy for the collection: # GET $endpoint/policystore/metadataPolicies?api-version=2021-07-01&collectionName=$collection # 2) In the returned policy, find the attributeRule whose id ends in ':role:$Role' # (e.g. purviewmetadatarole_builtin_data-curator) and add the principal id # '$CallerId' to its 'fromRule' attributeValueIncludes entries. # 3) PUT the modified policy back: # PUT $endpoint/policystore/metadataPolicies/{policyId}?api-version=2021-07-01 # See: https://learn.microsoft.com/purview/tutorial-metadata-policy-roles-apis Write-Warning "Purview role grants require a metadata-policy edit; review and apply the steps above for '$Role' / '$CallerId'." "@ $remove = @" # PSAutoRBAC: revoke Purview data-plane role '$Role' from '$CallerId' on collection '$collection'. # Reverse of the grant: GET the metadata policy, remove '$CallerId' from the # attributeRule for ':role:$Role', then PUT the policy back. Run as a Collection Admin. Write-Warning "Purview role revokes require a metadata-policy edit; remove '$CallerId' from the '$Role' rule and PUT the policy back." "@ @{ AddScript = $add; RemoveScript = $remove } } } } Register-RBACProvider -Provider (New-PurviewProbeProvider) |