Skip to content

Conversation

@alexandair
Copy link
Collaborator

New Azure REST and Resource Graph support:

Caching infrastructure improvements:

Other changes:

Copy link
Contributor

Copilot AI left a 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 around Invoke-AzRestMethod) with caching, optional OData query params, and default paging behavior for GETs.
  • Added Invoke-ZtAzureResourceGraphRequest to run KQL against ARG with $skipToken pagination and flattened results.
  • Introduced __ZtSession.AzureCache plus 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.

Comment on lines +76 to +79
catch {
Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning
Add-ZtTestResultDetail -SkippedBecause NotSupported
return
Copy link

Copilot AI Feb 12, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +85
if ($results.Count -eq 0) {
Write-PSFMessage "No firewall policies found." -Tag Test -Level VeryVerbose
Add-ZtTestResultDetail -SkippedBecause NotSupported
return
Copy link

Copilot AI Feb 12, 2026

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.

Copilot uses AI. Check for mistakes.
…-ZtAzureResourceGraphRequest to use the new function
@alexandair alexandair changed the title Add Invoke-ZtRestMethod and Invoke-ZtAzureResourceGraphRequest functions Add Invoke-ZtAzureRequest and Invoke-ZtAzureResourceGraphRequest functions Feb 12, 2026
Copilot AI review requested due to automatic review settings February 12, 2026 11:00
Copy link
Contributor

Copilot AI left a 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.

Comment on lines +248 to +249
# ByParameters set: build a key from the components
$cacheKey = '{0}/{1}/{2}/{3}/{4}' -f $SubscriptionId, $ResourceGroupName, $ResourceProviderName, ($ResourceType -join '/'), ($Name -join '/')
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
if ($Filter) { $extraQueryParams['$filter'] = $Filter }
if ($Top) { $extraQueryParams['$top'] = $Top }
if ($QueryParameters) {
foreach ($key in $QueryParameters.Keys) {
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
foreach ($key in $QueryParameters.Keys) {
foreach ($key in ($QueryParameters.Keys | Sort-Object)) {

Copilot uses AI. Check for mistakes.
$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).
Copy link

Copilot AI Feb 12, 2026

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).

Suggested change
Query Azure Resource Graph using POST with pagination (follows $skipToken automatically).
Query Azure Resource Graph using POST.

Copilot uses AI. Check for mistakes.
}
catch {
Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning
Add-ZtTestResultDetail -SkippedBecause NotSupported
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 12, 2026 11:23
Copy link
Contributor

Copilot AI left a 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.

Comment on lines +57 to +69
# 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'
}
}
Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
# 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]

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +44

Query Azure Resource Graph using POST with pagination (follows $skipToken automatically).

Copy link

Copilot AI Feb 12, 2026

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.

Suggested change
Query Azure Resource Graph using POST with pagination (follows $skipToken automatically).
Query Azure Resource Graph using POST. This cmdlet does not automatically follow $skipToken.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant