Azure / Azure/deployment-stacks
Evaluate deny assignments for Move operations, or block Move of stack-managed resources
- Dominant language
- Bicep
- Stars
- 101
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Description
**Is your feature request related to a problem? Please describe.**
Moving a stack-managed resource to another resource group silently removes its deny-assignment protection. This is already acknowledged in the docs, on the [Known issues for deployment stacks](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-stacks-known-issues) page ("Moving a managed resource removes its stack protection"), added in the [23 June 2026 docs split](https://github.com/MicrosoftDocs/azure-docs/commit/d4d7376a639d74776c049812034d93197bc2765d):
> Deny assignments are evaluated at the resource group scope for Move operations, not at the individual resource scope. As a result, a resource can be moved out of its resource group even when a deny assignment protects that individual resource, and a deployment stack's protections don't transfer with the resource after it's moved. [...] As an interim mitigation, apply a read-only lock on the resource group to block Move operations.
I'm raising this because the docs describe the read-only lock as an *interim* mitigation, which implies a real fix is intended, but I can't find any public tracking item for it. There's nothing open in this repo, no release note, and no Azure Update. This issue is meant to be that tracking item rather than a new bug report.
There are two distinct effects, and the second one gets less attention:
1. **Protection is bypassed at move time.** The move is authorised by `Microsoft.Resources/subscriptions/resourceGroups/moveResources/action` on the source RG and `.../write` on the destination. Both are resource-group scoped. The stack's deny assignment is scoped to the individual resource, and deny assignments inherit downward, never upward, so the authorisation check for a Move never consults it. The assignment isn't overridden or outranked, it's simply at a scope that isn't read.
2. **The stack's own state is then wrong, and reports no error.** After the move, the stack still lists the resource with `status: managed` and `denyStatus: denyWriteAndDelete`, against a resource ID in the source RG where nothing exists any more. It isn't flagged as out of sync or failed. The managed resource list is the thing an operator would check to confirm protection, and it confidently reports protection over a resource that left. For anyone using stacks as an audit or compliance control, that second point is arguably the more serious of the two.
**Describe the solution you'd like**
Any one of these would close the gap, roughly in order of preference:
1. Evaluate deny assignments for Move operations against the resources being moved, not only the resource group scope, so a `denyWriteAndDelete` assignment on a resource blocks that resource being moved out.
2. Failing that, have the resource provider refuse a Move of a stack-managed resource while a deny setting applies to it, in the same way a ReadOnly lock does today, without operators having to add and manage the lock themselves.
3. At minimum, make the stack detect it. If a managed resource ID no longer resolves, the stack should surface that as an unhealthy or out-of-sync state rather than continuing to report `managed` / `denyWriteAndDelete`. This is the smallest change and would remove the false assurance even if the move itself stays possible.
**Describe alternatives you've considered**
- **ReadOnly lock on the resource group**, as the docs recommend. I've verified this works: the move fails with `ScopeLocked`, and stack reads plus deny-delete protection keep working with the lock in place. The cost is that stack updates are blocked too, so you end up removing and reapplying the lock around every deployment. That's a manual step in a process whose entire point is to remove manual steps, and it's one that gets forgotten.
- **`denySettingsApplyToChildScopes`**, which doesn't help. It extends the assignment downward to child scopes, and the problem here is that the Move check happens at the parent scope.
- **Detecting it after the fact** via activity log or drift checks. This is reactive by definition, and the stack's own state won't tell you.
**Additional context**
Reproduction lab: https://github.com/awood-ops/deployment-stacks-lab (Bicep template plus a PowerShell harness, creates its own resource groups and tears them down).
- **Test 5** is the repro. Deploy a stack with `denyWriteAndDelete`, then `az resource move` a managed storage account to another RG. The move succeeds.
- **Test 7** repeats it with a ReadOnly lock on the source RG and confirms the documented mitigation blocks it.
- **Test 2** dumps the deny assignment the stack created. Its `scope` is the individual resource and `excludePrincipals` is empty with the all-principals wildcard, which is the evidence that the move isn't succeeding because of who is running it.
One caveat, stated plainly: the lab includes a **test 8** that repeats the sequence as a Contributor-only service principal, to rule out privilege as the explanation. I have not been able to run it, because the tenant I tested in wouldn't let me create the app registration. The scope reasoning above and the empty `excludePrincipals` are why I'm confident it isn't a privilege artefact, but I'd rather flag that as reasoned than claim I observed it.
Tested with Azure CLI 2.61.0+ against resource-group scoped stacks, storage account as the move subject.
Background write-up, for context rather than as the report: https://awood.tech/azure-deployment-stacks-deny-settings-move-gap/
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with Test 5 in the linked deployment-stacks-lab and compare it with Test 7's ReadOnly-lock mitigation; inspect Test 2 for the deny-assignment scope evidence. Test 8 is an unverified follow-up. Done requires a decided, tracked behavior for blocking the move or reporting the stack state accurately, with the reproduction results confirmed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, powershell
- Domain
- cloud, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100