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
- 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.
- Open a pull request that adds a resource — in our case
Microsoft.AlertsManagement/actionRules and microsoft.insights/actionGroups.
- 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
Summary
New-AzOpsDeploymentnever passes-ValidationLevelto the what-if cmdlets, so ARM always applies the defaultProvider.Providerruns preflight as a real deployment and checks write permission on every resource in the template. The practical result is thatInvoke-AzOpsPush -WhatIfcannot run under a least-privilege identity — the Validate pipeline must use the same principal as Push.ARM already supports the fix.
ValidationLevel: ProviderNoRbacperforms 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.ps1the what-if call is:$parametersis built up through the function and never contains aValidationLevelentry. There is no PSFConfig setting for it either — the only what-if related key isCore.WhatifExcludedChangeTypes.Repro
*/read,Microsoft.Resources/deployments/whatIf/actionandMicrosoft.Resources/deployments/validate/action, and no write on any resource type.Microsoft.AlertsManagement/actionRulesandmicrosoft.insights/actionGroups.Granting
Microsoft.Resources/deployments/whatIf/actionalone 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-Acceleratorships a singlecredentialsvariable group shared bypull.yml,validate.ymlandpush.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
Providerso existing behaviour is unchanged:and add it to the splat in
New-AzOpsDeploymentwhen a what-if is being performed, e.g.Operators who want a read-only preview identity could then set
ProviderNoRbacand pair it with a custom role of*/readplus the two preview actions.AzOps-Acceleratorcould 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:
This is unsupported, relies on the global scope reaching into module scope, does not reach the
ForEach-Object -Parallelrunspaces used whenAllowMultipleTemplateParameterFilesandParallelDeployMultipleTemplateParameterFilesare enabled, and fails silently by reverting toProviderrather than reporting that the setting was ignored.Versions
mainat time of writingAzOps-AcceleratorValidationLevelrequires Az PowerShell 13.4.0+ / Azure CLI 2.76.0+References