modules/Devolutions.CIEM.Checks/Public/Get-CIEMRequiredPermission.ps1
|
function Get-CIEMRequiredPermission { <# .SYNOPSIS Gets the required permissions for running CIEM security checks, discovery endpoints, and Azure remediations. .DESCRIPTION Aggregates all unique permissions required across all enabled checks and discovery endpoints registered in azure_provider_apis. For Azure without service or check filters, it also derives remediation permissions from the shared remediation token catalog and the attack path remediation templates. Returns permissions grouped by type, depending on the provider: - Azure: Microsoft Graph API, Azure Resource Manager RBAC, Key Vault data plane, Azure Roles - AWS: IAM actions (e.g., iam:ListUsers, s3:GetBucketPolicy) Discovery endpoint permissions (e.g., Directory.Read.All for Entra entity collection) are always included for the relevant provider, regardless of check filters. .PARAMETER Provider Filter to permissions for a specific cloud provider (Azure, AWS). .PARAMETER Service Filter to permissions required for a specific service (e.g., Entra, IAM, KeyVault, Storage, iam, s3). .PARAMETER CheckId Filter to permissions required for specific check IDs. .OUTPUTS [PSCustomObject] Object containing provider-appropriate permission properties: Azure: Graph, ARM, KeyVaultDataPlane, AzureRoles, CheckCount, DiscoveryEndpointCount, Summary, Discovery, Remediation AWS: IAM, CheckCount, Summary .EXAMPLE Get-CIEMRequiredPermission -Provider Azure # Returns all Azure discovery and remediation permissions for Azure .EXAMPLE Get-CIEMRequiredPermission -Provider AWS # Returns all IAM actions required for AWS checks .EXAMPLE Get-CIEMRequiredPermission -Service Entra # Returns permissions required for Entra ID checks and discovery endpoints #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [ValidateSet('Azure', 'AWS')] [string]$Provider, [Parameter()] [string]$Service, [Parameter()] [string[]]$CheckId ) $ErrorActionPreference = 'Stop' function GetCIEMRemediationPermissionCatalog { $ErrorActionPreference = 'Stop' $catalogPath = Join-Path $script:ModuleRoot 'modules' 'Devolutions.CIEM.Checks' 'Data' 'remediation-permissions.json' if (-not (Test-Path $catalogPath)) { throw "CIEM remediation permission catalog not found: $catalogPath" } $catalog = Get-Content $catalogPath -Raw | ConvertFrom-Json -AsHashtable if ($null -eq $catalog.Azure -or $null -eq $catalog.Azure.RemediationTokens) { throw "CIEM remediation permission catalog is missing Azure.RemediationTokens: $catalogPath" } $catalog } function GetCIEMAzureRemediationPermissionSection { param( [Parameter(Mandatory)] [hashtable]$Catalog ) $ErrorActionPreference = 'Stop' $templateRoot = Join-Path $script:ModuleRoot 'modules' 'Devolutions.CIEM.Graph' 'Data' 'attack_path_remediation_scripts' if (-not (Test-Path $templateRoot)) { throw "CIEM attack path remediation script folder not found: $templateRoot" } $tokenMap = $Catalog.Azure.RemediationTokens $graphPermissions = [System.Collections.Generic.List[string]]::new() $azureRoles = [System.Collections.Generic.List[string]]::new() $matchedTemplateCount = 0 foreach ($template in @(Get-ChildItem $templateRoot -File)) { $content = Get-Content $template.FullName -Raw $tokens = @( [regex]::Matches($content, '{{([A-Z0-9_]+)}}') | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique ) $matchedTemplate = $false foreach ($token in $tokens) { if (-not $tokenMap.ContainsKey($token)) { continue } $matchedTemplate = $true $requirements = $tokenMap[$token] foreach ($permission in @($requirements.Graph)) { if ([string]::IsNullOrWhiteSpace([string]$permission)) { continue } $graphPermissions.Add([string]$permission) } foreach ($role in @($requirements.AzureRoles)) { if ([string]::IsNullOrWhiteSpace([string]$role)) { continue } $azureRoles.Add([string]$role) } } if ($matchedTemplate) { $matchedTemplateCount++ } } [PSCustomObject]@{ Graph = @($graphPermissions | Select-Object -Unique | Sort-Object) AzureRoles = @($azureRoles | Select-Object -Unique | Sort-Object) TemplateCount = $matchedTemplateCount } } # Get checks based on filters $getCheckParams = @{} if ($Provider) { $getCheckParams.Provider = $Provider } if ($Service) { $getCheckParams.Service = $Service } $checks = Get-CIEMCheck @getCheckParams if ($CheckId) { $checks = $checks | Where-Object { $CheckId -contains $_.Id } } # Aggregate unique permissions using List for efficient collection $graphPermissions = [System.Collections.Generic.List[string]]::new() $armPermissions = [System.Collections.Generic.List[string]]::new() $kvPermissions = [System.Collections.Generic.List[string]]::new() $iamPermissions = [System.Collections.Generic.List[string]]::new() $endpointAzureRoles = [System.Collections.Generic.List[string]]::new() $remediationSection = [PSCustomObject]@{ Graph = @() AzureRoles = @() TemplateCount = 0 } # Aggregate from checks $checkCount = 0 if ($checks) { $checkCount = @($checks).Count foreach ($check in $checks) { $perms = $check.Permissions if ($perms.Graph) { foreach ($p in $perms.Graph) { $graphPermissions.Add($p) } } if ($perms.ARM) { foreach ($p in $perms.ARM) { $armPermissions.Add($p) } } if ($perms.KeyVaultDataPlane) { foreach ($p in $perms.KeyVaultDataPlane) { $kvPermissions.Add($p) } } if ($perms.IAM) { foreach ($p in $perms.IAM) { $iamPermissions.Add($p) } } } } # Aggregate discovery endpoint permissions from azure_provider_apis (scoped rows) $discoveryEndpointCount = 0 if ($Provider -ne 'AWS') { $endpointParams = @{ HasPermissions = $true } if ($Service) { $endpointParams.Service = $Service } $endpoints = @(GetCIEMAzureProviderApi @endpointParams) $discoveryEndpointCount = $endpoints.Count foreach ($endpoint in $endpoints) { $perms = $endpoint.Permissions if ($perms.Graph) { foreach ($p in $perms.Graph) { $graphPermissions.Add($p) } } if ($perms.ARM) { foreach ($p in $perms.ARM) { $armPermissions.Add($p) } } if ($perms.KeyVaultDataPlane) { foreach ($p in $perms.KeyVaultDataPlane) { $kvPermissions.Add($p) } } if ($perms.AzureRoles) { foreach ($p in $perms.AzureRoles) { $endpointAzureRoles.Add($p) } } } } $includeAzureRemediation = ($Provider -ne 'AWS' -and -not $PSBoundParameters.ContainsKey('Service') -and -not $PSBoundParameters.ContainsKey('CheckId')) if ($includeAzureRemediation) { $remediationSection = GetCIEMAzureRemediationPermissionSection -Catalog (GetCIEMRemediationPermissionCatalog) } $hasRemediationPermissions = ($remediationSection.Graph.Count -gt 0 -or $remediationSection.AzureRoles.Count -gt 0) if (-not $checks -and $discoveryEndpointCount -eq 0 -and -not $hasRemediationPermissions) { Write-Warning "No checks or discovery endpoints found matching the specified criteria." return [PSCustomObject]@{ Graph = @() ARM = @() KeyVaultDataPlane = @() AzureRoles = @() IAM = @() CheckCount = 0 DiscoveryEndpointCount = 0 Discovery = [PSCustomObject]@{ Graph = @() ARM = @() KeyVaultDataPlane = @() AzureRoles = @() CheckCount = 0 DiscoveryEndpointCount = 0 } Remediation = $remediationSection Summary = "No checks or discovery endpoints found." } } # Get unique and sort (wrap in @() to ensure arrays) $graphPermissions = @($graphPermissions | Select-Object -Unique | Sort-Object) $armPermissions = @($armPermissions | Select-Object -Unique | Sort-Object) $kvPermissions = @($kvPermissions | Select-Object -Unique | Sort-Object) $iamPermissions = @($iamPermissions | Select-Object -Unique | Sort-Object) # Determine required Azure RBAC roles based on check permissions + endpoint permissions $azureRoles = @() if ($graphPermissions.Count -gt 0 -or $armPermissions.Count -gt 0 -or $kvPermissions.Count -gt 0 -or $endpointAzureRoles.Count -gt 0) { # Subscription Reader is always required for subscription discovery (ARM REST API) $azureRoles = @('Reader') # ARM permissions: Reader role covers all */read actions if ($armPermissions.Count -gt 0) { $nonReadPermissions = @($armPermissions | Where-Object { $_ -notmatch '/read$' }) if ($nonReadPermissions.Count -gt 0) { Write-Warning "Some ARM permissions require write access. Review permissions and assign appropriate roles." } } # Key Vault data plane permissions: Map to specific RBAC roles if ($kvPermissions -contains 'secrets/list' -or $kvPermissions -contains 'secrets/get') { $azureRoles += 'Key Vault Secrets User' } if ($kvPermissions -contains 'keys/list' -or $kvPermissions -contains 'keys/get') { $azureRoles += 'Key Vault Crypto User' } # Merge explicit AzureRoles from discovery endpoints foreach ($role in $endpointAzureRoles) { $azureRoles += $role } $azureRoles = @($azureRoles | Select-Object -Unique | Sort-Object) } # Build summary $summaryParts = @() $summaryParts += "Permissions required for $checkCount check(s) and $discoveryEndpointCount discovery endpoint(s):" if ($graphPermissions.Count -gt 0) { $summaryParts += "" $summaryParts += "Microsoft Graph API Permissions (Application):" foreach ($perm in $graphPermissions) { $summaryParts += " - $perm" } } if ($armPermissions.Count -gt 0) { $summaryParts += "" $summaryParts += "Azure Resource Manager RBAC Actions:" foreach ($perm in $armPermissions) { $summaryParts += " - $perm" } } if ($kvPermissions.Count -gt 0) { $summaryParts += "" $summaryParts += "Key Vault Data Plane Permissions:" foreach ($perm in $kvPermissions) { $summaryParts += " - $perm" } } if ($azureRoles.Count -gt 0) { $summaryParts += "" $summaryParts += "Required Azure RBAC Roles (assign at subscription scope):" $summaryParts += " - Reader (required for subscription discovery)" foreach ($role in @($azureRoles | Where-Object { $_ -ne 'Reader' })) { $summaryParts += " - $role" } } if ($discoveryEndpointCount -gt 0) { $summaryParts += "" $summaryParts += "Discovery Endpoint Permissions ($discoveryEndpointCount endpoints):" $summaryParts += " Permissions above include requirements for data collection endpoints" $summaryParts += " registered in azure_provider_apis (Entra, ResourceGraph, IAM)." } if ($iamPermissions.Count -gt 0) { $summaryParts += "" $summaryParts += "AWS IAM Actions:" foreach ($perm in $iamPermissions) { $summaryParts += " - $perm" } } $discoverySection = [PSCustomObject]@{ Graph = @($graphPermissions) ARM = @($armPermissions) KeyVaultDataPlane = @($kvPermissions) AzureRoles = @($azureRoles) CheckCount = $checkCount DiscoveryEndpointCount = $discoveryEndpointCount } [PSCustomObject]@{ Graph = @($graphPermissions) ARM = @($armPermissions) KeyVaultDataPlane = @($kvPermissions) AzureRoles = @($azureRoles) IAM = @($iamPermissions) CheckCount = $checkCount DiscoveryEndpointCount = $discoveryEndpointCount Discovery = $discoverySection Remediation = $remediationSection Summary = $summaryParts -join "`n" } } |