-
Notifications
You must be signed in to change notification settings - Fork 124
Add Invoke-ZtAzureRequest and Invoke-ZtAzureResourceGraphRequest functions #890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…d caching and parameter handling
…source Graph with pagination support
…e on Azure Firewall
# Conflicts: # src/powershell/tests/Test-Assessment.25537.ps1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds first-class Azure REST and Azure Resource Graph (ARG) request helpers to the ZeroTrustAssessment PowerShell module, including session-scoped caching, paging support, and a test refactor to use ARG for Azure Firewall policy evaluation.
Changes:
- Added
Invoke-ZtRestMethod(proxy aroundInvoke-AzRestMethod) with caching, optional OData query params, and default paging behavior for GETs. - Added
Invoke-ZtAzureResourceGraphRequestto run KQL against ARG with$skipTokenpagination and flattened results. - Introduced
__ZtSession.AzureCacheplus cache clearing, exported the new public functions, and refactored Test 25537 to query via ARG.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/powershell/public/Invoke-ZtRestMethod.ps1 | New public REST helper wrapping Invoke-AzRestMethod with cache/paging/OData conveniences. |
| src/powershell/private/core/Invoke-ZtRestMethodCache.ps1 | New internal cache executor for Azure REST calls (GET caching, error handling). |
| src/powershell/public/Invoke-ZtAzureResourceGraphRequest.ps1 | New public ARG wrapper with $skipToken pagination. |
| src/powershell/scripts/variables.ps1 | Adds session-scoped AzureCache store to __ZtSession. |
| src/powershell/private/core/Clear-ZtModuleVariable.ps1 | Clears AzureCache alongside other session caches. |
| src/powershell/ZeroTrustAssessment.psd1 | Exports Invoke-ZtRestMethod and Invoke-ZtAzureResourceGraphRequest. |
| src/powershell/tests/Test-Assessment.25537.ps1 | Refactors Azure Firewall Threat Intel test to use ARG instead of per-subscription ARM enumeration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| catch { | ||
| Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning | ||
| Add-ZtTestResultDetail -SkippedBecause NotSupported | ||
| return |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The catch block maps any Azure Resource Graph failure to -SkippedBecause NotSupported, which can mask real failures (e.g., transient errors or 403/no access) and makes troubleshooting harder. Consider inspecting the exception/HTTP status and using a more specific skip reason (e.g., NoAzureAccess for 403) or rethrowing unexpected errors.
| if ($results.Count -eq 0) { | ||
| Write-PSFMessage "No firewall policies found." -Tag Test -Level VeryVerbose | ||
| Add-ZtTestResultDetail -SkippedBecause NotSupported | ||
| return |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When no firewall policies are found, the test is currently skipped as NotSupported. In other Azure network tests, “no resources/policies to evaluate” is treated as NotApplicable (e.g., Test-Assessment.25539). Using NotApplicable here would better reflect that the environment is supported, but there’s nothing to assess.
…-ZtAzureResourceGraphRequest to use the new function
…Invoke-ZtAzureRequest function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # ByParameters set: build a key from the components | ||
| $cacheKey = '{0}/{1}/{2}/{3}/{4}' -f $SubscriptionId, $ResourceGroupName, $ResourceProviderName, ($ResourceType -join '/'), ($Name -join '/') |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache key for the ByParameters parameter set does not include ApiVersion, so the same resource requested with different API versions will collide in cache and can return incorrect data. Include ApiVersion (and any other URL-affecting components) in the cache key for this parameter set.
| # ByParameters set: build a key from the components | |
| $cacheKey = '{0}/{1}/{2}/{3}/{4}' -f $SubscriptionId, $ResourceGroupName, $ResourceProviderName, ($ResourceType -join '/'), ($Name -join '/') | |
| # ByParameters set: build a key from the components, including ApiVersion | |
| $cacheKey = '{0}/{1}/{2}/{3}/{4}/{5}' -f $SubscriptionId, $ResourceGroupName, $ResourceProviderName, ($ResourceType -join '/'), ($Name -join '/'), $ApiVersion |
| if ($Filter) { $extraQueryParams['$filter'] = $Filter } | ||
| if ($Top) { $extraQueryParams['$top'] = $Top } | ||
| if ($QueryParameters) { | ||
| foreach ($key in $QueryParameters.Keys) { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QueryParameters.Keys iteration order is non-deterministic for normal hashtables, so the generated query string (and therefore the request URL and cache key) can vary across runs even with the same input. To keep URLs/caching stable, consider ordering the keys (or requiring/normalizing to an [ordered] dictionary) before appending them.
| foreach ($key in $QueryParameters.Keys) { | |
| foreach ($key in ($QueryParameters.Keys | Sort-Object)) { |
| $body = @{ query = "Resources | where type =~ 'microsoft.compute/virtualmachines'" } | ConvertTo-Json | ||
| Invoke-ZtAzureRequest -Path "/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01" -Method POST -Payload $body | ||
|
|
||
| Query Azure Resource Graph using POST with pagination (follows $skipToken automatically). |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment-based help example claims POST-based Resource Graph queries via Invoke-ZtAzureRequest “follow $skipToken automatically”, but Invoke-ZtAzureRequest only enables built-in ARM pagination for GET (-Paginate) and does not implement $skipToken request-body pagination. Update the example/description to avoid implying skipToken support in this function (that behavior lives in Invoke-ZtAzureResourceGraphRequest).
| Query Azure Resource Graph using POST with pagination (follows $skipToken automatically). | |
| Query Azure Resource Graph using POST. |
| } | ||
| catch { | ||
| Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning | ||
| Add-ZtTestResultDetail -SkippedBecause NotSupported |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ARG query failure path always marks the test as NotSupported, but failures here can also be due to insufficient Azure RBAC (e.g. 403) and should likely map to NoAzureAccess (consistent with other Azure tests). Consider inspecting the thrown error/status code (e.g., by extending the wrapper to surface StatusCode or adding a -FullResponse option) so the skip reason is accurate.
| Add-ZtTestResultDetail -SkippedBecause NotSupported | |
| # Distinguish between lack of Azure access (e.g. 403) and other failures | |
| $skipReason = 'NotSupported' | |
| $statusCode = $null | |
| if ($_.Exception -and ($_.Exception.PSObject.Properties.Name -contains 'StatusCode')) { | |
| $statusCode = $_.Exception.StatusCode | |
| } | |
| elseif ($_.Exception.InnerException -and ($_.Exception.InnerException.PSObject.Properties.Name -contains 'StatusCode')) { | |
| $statusCode = $_.Exception.InnerException.StatusCode | |
| } | |
| if ($statusCode -eq 403 -or "$statusCode" -eq 'Forbidden' -or $_.Exception.Message -match '403|AuthorizationFailed|Authorization failed|insufficient privileges') { | |
| $skipReason = 'NoAzureAccess' | |
| } | |
| Add-ZtTestResultDetail -SkippedBecause $skipReason |
…sourceGraphRequest function
…esponse for cached content
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Check cache for GET requests | ||
| if (-not $cacheBlocked -and -not $DisableCache -and $isGet -and $isInCache) { | ||
| Write-PSFMessage "Using Azure cache: $($CacheKey)" -Level Debug | ||
| $cachedResult = $script:__ZtSession.AzureCache.Value[$CacheKey] | ||
| if ($FullResponse) { | ||
| # Return a synthetic response object for cached content | ||
| return [PSCustomObject]@{ | ||
| StatusCode = 200 | ||
| Content = $cachedResult | ConvertTo-Json -Depth 100 | ||
| Headers = @{} | ||
| Method = 'GET' | ||
| } | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When serving a cached result with -FullResponse, the function returns a synthetic [PSCustomObject] rather than the PSHttpResponse type returned by Invoke-AzRestMethod. Callers that rely on the response type (or additional properties) may break. Consider either (1) documenting that cached -FullResponse is a simplified object, or (2) bypassing cache when -FullResponse is used.
| # Check cache for GET requests | |
| if (-not $cacheBlocked -and -not $DisableCache -and $isGet -and $isInCache) { | |
| Write-PSFMessage "Using Azure cache: $($CacheKey)" -Level Debug | |
| $cachedResult = $script:__ZtSession.AzureCache.Value[$CacheKey] | |
| if ($FullResponse) { | |
| # Return a synthetic response object for cached content | |
| return [PSCustomObject]@{ | |
| StatusCode = 200 | |
| Content = $cachedResult | ConvertTo-Json -Depth 100 | |
| Headers = @{} | |
| Method = 'GET' | |
| } | |
| } | |
| # Check cache for GET requests (only for non-FullResponse calls) | |
| if (-not $cacheBlocked -and -not $DisableCache -and $isGet -and $isInCache -and -not $FullResponse) { | |
| Write-PSFMessage "Using Azure cache: $($CacheKey)" -Level Debug | |
| $cachedResult = $script:__ZtSession.AzureCache.Value[$CacheKey] |
|
|
||
| Query Azure Resource Graph using POST with pagination (follows $skipToken automatically). | ||
|
|
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The help example states Invoke-ZtAzureRequest “follows $skipToken automatically” for Azure Resource Graph. This function doesn’t implement Resource Graph’s $skipToken pagination (and it explicitly only auto-paginates GET via -Paginate). Update the example to use Invoke-ZtAzureResourceGraphRequest or adjust the text so it doesn’t claim $skipToken support here.
| Query Azure Resource Graph using POST with pagination (follows $skipToken automatically). | |
| Query Azure Resource Graph using POST. This cmdlet does not automatically follow $skipToken. | |
New Azure REST and Resource Graph support:
Added the
Invoke-ZtRestMethodpublic function, a proxy forInvoke-AzRestMethodthat provides session-scoped caching for GET requests, automatic pagination, OData parameter support (-Select,-Filter,-Top), and improved error handling. (src/powershell/public/Invoke-ZtRestMethod.ps1, src/powershell/public/Invoke-ZtRestMethod.ps1R1-R281)Added the
Invoke-ZtAzureResourceGraphRequestpublic function, which wrapsInvoke-ZtRestMethodto query Azure Resource Graph with KQL, handling$skipTokenpagination and returning a flat array of results. (src/powershell/public/Invoke-ZtAzureResourceGraphRequest.ps1, src/powershell/public/Invoke-ZtAzureResourceGraphRequest.ps1R1-R118)Exported the new public functions in the module manifest (
src/powershell/ZeroTrustAssessment.psd1, src/powershell/ZeroTrustAssessment.psd1L79-R80)Caching infrastructure improvements:
Introduced a new
AzureCachesession variable to store cached Azure REST GET results, ensuring efficient repeated queries within a session. (src/powershell/scripts/variables.ps1, src/powershell/scripts/variables.ps1R6)Updated the
Clear-ZtModuleVariablefunction to clear the newAzureCachealongside other session-scoped caches. (src/powershell/private/core/Clear-ZtModuleVariable.ps1, src/powershell/private/core/Clear-ZtModuleVariable.ps1R18)Implemented the internal
Invoke-ZtRestMethodCachefunction to manage cache logic, including cache bypass, storing/retrieving results, and error handling for REST requests. (src/powershell/private/core/Invoke-ZtRestMethodCache.ps1, src/powershell/private/core/Invoke-ZtRestMethodCache.ps1R1-R122)Other changes:
Invoke-ZtAzureResourceGraphRequestfunction. (src/powershell/tests/Test-Assessment.25537.ps1, src/powershell/tests/Test-Assessment.25537.ps1L36)