Skip to content

Expose ValidationLevel so what-if can run under a least-privilege identity #954

Description

Summary

New-AzOpsDeployment never passes -ValidationLevel to the what-if cmdlets, so ARM always applies the default Provider. Provider runs preflight as a real deployment and checks write permission on every resource in the template. The practical result is that Invoke-AzOpsPush -WhatIf cannot run under a least-privilege identity — the Validate pipeline must use the same principal as Push.

ARM already supports the fix. ValidationLevel: ProviderNoRbac performs full template and provider validation but checks only read permission per resource. AzOps just has no way to ask for it.

Current behaviour

In src/internal/functions/New-AzOpsDeployment.ps1 the what-if call is:

$results = & $whatIfCommand @parameters -ErrorAction Continue -ErrorVariable resultsError

$parameters is built up through the function and never contains a ValidationLevel entry. There is no PSFConfig setting for it either — the only what-if related key is Core.WhatifExcludedChangeTypes.

Repro

  1. Give the Validate pipeline identity a role with */read, Microsoft.Resources/deployments/whatIf/action and Microsoft.Resources/deployments/validate/action, and no write on any resource type.
  2. Open a pull request that adds a resource — in our case Microsoft.AlertsManagement/actionRules and microsoft.insights/actionGroups.
  3. Validate fails:
Get-AzResourceGroupDeploymentWhatIfResult: InvalidTemplateDeployment - Long running operation
failed with status 'Failed'. Additional Info:'The template deployment failed with error:
'Authorization failed for template resource '<name>' of type
'Microsoft.AlertsManagement/actionRules'. The client '***' with object id '<redacted>' does not
have permission to perform action 'Microsoft.AlertsManagement/actionRules/write' at scope
'/subscriptions/<redacted>/resourceGroups/<redacted>/providers/Microsoft.AlertsManagement/actionRules/<name>'.'.'

Granting Microsoft.Resources/deployments/whatIf/action alone is not enough — that clears the first error, and then preflight fails on the per-resource write check.

Why this matters

The usual CI pattern is a low-privilege identity for PR preview and a separate writer for deploy, so that a pull request from anyone cannot run under a principal able to change production. With AzOps, Validate has to hold deploy rights, because what-if demands them.

AzOps-Accelerator ships a single credentials variable group shared by pull.yml, validate.yml and push.yml, so this is consistent with the reference design today — but it means the split isn't available to anyone who wants it.

Proposal

Expose the ARM parameter as a setting, defaulting to Provider so existing behaviour is unchanged:

"Core.WhatIfValidationLevel": "Provider"

and add it to the splat in New-AzOpsDeployment when a what-if is being performed, e.g.

$validationLevel = Get-PSFConfigValue -FullName 'AzOps.Core.WhatIfValidationLevel'
if ($validationLevel) {
    $parameters.ValidationLevel = $validationLevel
}

Operators who want a read-only preview identity could then set ProviderNoRbac and pair it with a custom role of */read plus the two preview actions. AzOps-Accelerator could subsequently offer a separate validate identity, which is not possible while the module cannot pass the parameter.

Workaround considered and rejected

Injecting the parameter from the pipeline:

$global:PSDefaultParameterValues['Get-AzResourceGroupDeploymentWhatIfResult:ValidationLevel'] = 'ProviderNoRbac'

This is unsupported, relies on the global scope reaching into module scope, does not reach the ForEach-Object -Parallel runspaces used when AllowMultipleTemplateParameterFiles and ParallelDeployMultipleTemplateParameterFiles are enabled, and fails silently by reverting to Provider rather than reporting that the setting was ignored.

Versions

  • AzOps 2.8.5, and confirmed unchanged on main at time of writing
  • Azure DevOps pipelines from AzOps-Accelerator
  • ValidationLevel requires Az PowerShell 13.4.0+ / Azure CLI 2.76.0+

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions