Preflight check: detect Azure Policy blocking local authentication#7179
Preflight check: detect Azure Policy blocking local authentication#7179
Conversation
Add a local preflight check that detects Azure Policy assignments which deny resources with local authentication enabled (disableLocalAuth != true). The check: - Lists policy assignments on the target subscription via armpolicy SDK - Fetches policy definitions and inspects policyRule for disableLocalAuth field conditions with deny effect - Handles parameterized effects, policy sets (initiatives), and nested allOf/anyOf conditions - Cross-references affected resource types against the Bicep snapshot - Reports a warning (not error) when template resources would be blocked Also handles storage accounts which use allowSharedKeyAccess (inverted logic) instead of disableLocalAuth. Fixes #7177 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new local (client-side) preflight check in azd provision that warns when Azure Policy assignments on the target subscription are likely to deny deployments of resources that have local authentication enabled, helping users avoid late RequestDisallowedByPolicy failures.
Changes:
- Introduces
PolicyService(Azure Policy assignments/definitions enumeration + rule parsing) to detect deny policies related todisableLocalAuth/allowSharedKeyAccess. - Adds a Bicep preflight check that compares detected deny policies against
bicep snapshotpredicted resources and emits a warning when violations are found. - Registers the new service in the IoC container and adds the
armpolicySDK dependency + cspell overrides.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go |
Registers and implements the new local preflight check that warns based on snapshot resources and detected deny policies. |
cli/azd/pkg/azapi/policy_service.go |
New Azure Policy service to list assignments, fetch definitions (including initiatives), and parse policy rules for local-auth deny patterns. |
cli/azd/pkg/azapi/policy_service_test.go |
Unit tests for effect resolution, rule parsing helpers, and resource property evaluation. |
cli/azd/cmd/container.go |
Adds IoC registration for PolicyService. |
cli/azd/go.mod |
Adds armpolicy dependency (currently marked indirect). |
cli/azd/go.sum |
Adds checksums for the new armpolicy dependency. |
cli/azd/.vscode/cspell.yaml |
Adds spelling overrides for policy-related identifiers/terms. |
You can also share your feedback on Copilot code review. Take the survey.
- Fix extractParameterReference whitespace bug: use trimmed string for both prefix check and inner extraction - Remove localAuthEnabled from isLocalAuthField to avoid false positives (ResourceHasLocalAuthDisabled doesn't handle it) - Handle multiple resource types in policy 'in' array (was only taking first entry) - Thread resolved resourceType through recursive findInCompoundCondition calls so nested conditions inherit parent's resource type - Use []LocalAuthDenyPolicy per resource type to avoid overwriting when multiple deny policies target the same type - Add in-memory cache for policy definition/set definition fetches to avoid duplicate API calls across assignments - Improve warning message to mention both disableLocalAuth and allowSharedKeyAccess for storage accounts - Add tests for whitespace parameter extraction, multi-type 'in' arrays, and nested condition resource type inheritance - Run go mod tidy to move armpolicy to direct dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds log.Printf statements at key decision points so the flow can be traced with --debug to diagnose why the check might not fire: - Number of policy assignments found - Policy rule parse failures - Number of deny policies found - Per-resource type matching and localAuthDisabled status - Effect type mismatches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The subscription-level API lists inherited policy assignments from
management groups, but the definition IDs reference management group
scope (e.g. /providers/Microsoft.Management/managementGroups/{mgId}/...).
Previously, getPolicyDefinitionByID and getPolicySetDefinitionByID only
handled built-in and subscription-scoped definitions, silently failing
to fetch management-group-scoped ones. This caused the check to miss
policies like 'Storage Accounts - Safe Secrets Standard' which are
typically assigned at the management group level.
Fix: detect management group scope from the definition ID and use
GetAtManagementGroup SDK method. Also add diagnostic logging throughout
the check pipeline to aid debugging.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove subscription ID (redundant, already shown in context) - Remove unresolved ARM expression resource names (not helpful) - Aggregate by resource type instead of per-resource - Deduplicate policy names - Shorter, cleaner message format Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
wbreza
left a comment
There was a problem hiding this comment.
Code Review - PR #7179
What Looks Good
- Clean integration with the existing preflight framework - follows the same
PreflightCheckFnpattern ascheckRoleAssignmentPermissions - Well-structured
PolicyServicewith proper separation of concerns - ~90% of new code is inpkg/azapi/policy_service.go, reusable outside of Bicep - Excellent test coverage for policy rule parsing - 15+ test cases covering deny literals, parameterized effects, nested conditions, multi-type arrays, resource type inheritance, and edge cases
- Definition caching prevents redundant API calls; management-group scope handling is a nice touch for enterprise subs
- Modern Go patterns throughout -
maps.Copy,slices.Sorted(maps.Keys(...)),t.Parallel(),strings.Cut - All 11 copilot review issues addressed thoroughly
Summary
| Priority | Count |
|---|---|
| Medium | 3 |
| Low | 1 |
| Total | 4 |
Overall Assessment: Approve. Well-implemented feature with strong test coverage and clean integration. The main item worth considering is including policy names in the warning to make it more actionable.
Review performed with GitHub Copilot CLI
| "an Azure Policy on this subscription denies resources with local authentication enabled. "+ | ||
| "The following resource types in this deployment may be blocked:\n%s\n"+ | ||
| "Disable local authentication on these resources or request a policy exemption.", | ||
| strings.Join(lines, "\n"), |
There was a problem hiding this comment.
[Medium] Warning message omits policy names - less actionable for users
The policiesByType map stores policy names per resource type, but the warning only lists affected resource types. Users can't easily identify which policy is blocking them.
Consider including policy names in the output to make the warning more actionable:
for _, t := range typeList {
policies := policiesByType[strings.ToLower(t)]
names := make([]string, 0, len(policies))
for _, p := range policies {
names = append(names, p.PolicyName)
}
lines = append(lines, fmt.Sprintf(" - %s (policy: %s)", t, strings.Join(names, ", ")))
}|
|
||
| // Deploys the specified Bicep module and parameters with the selected provisioning scope (subscription vs resource group) | ||
| func (p *BicepProvider) deployModule( | ||
| ctx context.Context, |
There was a problem hiding this comment.
[Medium] No unit tests for checkLocalAuthPolicy integration logic
PolicyService parsing is well-tested (15+ cases), but this integration function has no test coverage. Untested paths: empty SnapshotResources exit, PolicyService resolution failure, API error fallback, no matching resource types, and warning message formatting.
Consider adding tests with mocked PolicyService and SnapshotResources.
| continue | ||
| } | ||
|
|
||
| if !azapi.ResourceHasLocalAuthDisabled(resource.Type, resource.Properties) { |
There was a problem hiding this comment.
[Low] Duplicate iteration over SnapshotResources
SnapshotResources is iterated twice - once for diagnostic logging (lines 2313-2318) and once for collecting affected types (lines 2322-2331). These could be merged into a single loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1a16458 to
c4fdb2c
Compare
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
Replace the custom client-side policy rule parsing engine with the server-side checkPolicyRestrictions API (Microsoft.PolicyInsights). The check is now scoped to storage accounts and calls the API in parallel for each account. Key changes: - PolicyService now uses armpolicyinsights.PolicyRestrictionsClient instead of manually fetching/parsing policy assignments & definitions - checkLocalAuthPolicy renamed to checkStorageAccountPolicy, filters snapshot resources to Microsoft.Storage/storageAccounts only - Parallel API calls per storage account via sync.WaitGroup.Go - Warning messages now include policy reasons from the server response - Replaced armpolicy SDK dependency with armpolicyinsights - Rewrote tests with mock HTTP transport for the new API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c4fdb2c to
feb6dfb
Compare
Overview
Adds a local preflight check that detects Azure Policy restrictions on storage accounts before deployment, preventing cryptic
RequestDisallowedByPolicyfailures.Problem
When a subscription has Azure Policies that restrict storage account configurations (e.g. requiring
allowSharedKeyAccess: false, enforcing specific SKUs, or blocking certain locations), deployments fail late with:Solution
A new preflight check that runs before deployment using the server-side
checkPolicyRestrictionsAPI (Microsoft.PolicyInsights):Microsoft.Storage/storageAccountsonlyCheckAtSubscriptionScopein parallel for each storage account — Azure's policy engine evaluates all assigned policies server-sideWhy server-side evaluation?
The initial approach reimplemented parts of the Azure Policy engine client-side (parsing policy rules, resolving parameters, etc.). Based on review feedback from @weikanglim, we switched to the
checkPolicyRestrictionsAPI which:Microsoft.PolicyInsights/*/read)Files Changed
pkg/azapi/policy_service.goPolicyServicenow usesarmpolicyinsights.PolicyRestrictionsClient.CheckAtSubscriptionScopewith parallel calls per resourcepkg/azapi/policy_service_test.gocheckPolicyRestrictionsAPI (deny from content eval, field restrictions, audit ignored, multiple resources, API error handling)pkg/infra/provisioning/bicep/bicep_provider.gocheckLocalAuthPolicy→checkStorageAccountPolicy: filters to storage accounts, callsCheckResourceRestrictions, formats per-account warningscmd/container.goPolicyService(unchanged constructor)go.mod/go.sumarmpolicywitharmpolicyinsightsSDK dependency.vscode/cspell.yamlLimitations (acceptable for a warning-level check)
Fixes #7177