Repository navigation
Customer onboarding: assess fallout from the invalid RBAC action in the cross-subscription ARM template #194
Description
Activity
- addedtriagedItem has been triagedItem has been triagedpriority/p1Next up; this sprintNext up; this sprintseverity/highSignificant harmSignificant harmurgency/this-sprintWithin the current sprintWithin the current sprintimpact/manyAffects most usersAffects most userseffort/mDaysDaystype/bugDefectDefect
on Aug 11, 2026 Correction: the
assignableScopessection of this issue is wrong. Please disregard it.I claimed the ARM template's
assignableScopesstill includes/providers/Microsoft.Capacitywhile the Terraform module had removed it, and suggested that as a possible second latent onboarding failure.That is not true on
main:"assignableScopes": [ "[concat('/subscriptions/', subscription().subscriptionId)]" ]
LeanerCloud/cloud-commitments-cli#1658 ("sec(iac): scope Azure purchase role to onboarded subscription") already fixed exactly this for LeanerCloud/cloud-commitments-cli#1545, and the role-parity checker's scope axis plus the
tenant-scope-arm.jsonfixture exist specifically to keep it fixed. There is nothing to investigate and nothing to file.I read the file from a checkout that was behind
origin/main, so I was looking at the pre-#1658 revision. Same mistake that made me undercount the affected fixtures at 8 when the real number was ~40.The rest of this issue stands and is unaffected, because it rests on the invalid action, which is independently confirmed against Azure's live operation catalog:
Microsoft.Capacity/reservationOrders/purchase/actiondoes not exist, and Azure rejects any role definition referencing it withInvalidActionOrNotAction(400).- That action is present in
arm/CUDly-CrossSubscription/template.jsononmain, so customer onboarding through this template should be failing. - The open questions about affected-customer count, partial deployment state, missing onboarding telemetry, and whether re-running the corrected template is idempotent are all still open.
One thing this correction strengthens: the assignableScopes regression guard demonstrably worked. The scope axis was caught, fixed, and pinned. What no test covered was whether the actions are things Azure will accept, which is the gap that let this ship.
Answering question 1: the affected window is 76 days
git log -S 'reservationOrders/purchase/action' -- arm/CUDly-CrossSubscription/template.jsonreturns exactly one commit that introduces the string:f34b036e2 2026-05-27 fix(iac/azure): custom role grants Microsoft.Capacity/...Removed 2026-08-11 by PR LeanerCloud/cloud-commitments-cli#1800.
So any customer who deployed
arm/CUDly-CrossSubscription/template.jsonbetween 2026-05-27 and 2026-08-11 should have hitInvalidActionOrNotAction(400) and failed to onboard. That is a 76-day window.The template itself predates this (first appears 2026-04-08,
485344dbd), so onboarding was plausibly working before 2026-05-27. Worth confirming whetherf34b036e2introduced the customroleDefinitionsresource wholesale or only added the bad action to an existing one, since that decides whether the pre-May-27 path was healthy or merely differently broken. I have not established that.What this does not answer
The remaining questions in this issue need data outside the repository and someone with access should take them:
- How many customers attempted onboarding in that window. The repo cannot answer this.
- Whether affected customers are in a partial state. The template declares role assignments that
dependsOnthe role definition, so they should not have been created, but ARM partial-deployment behaviour should be confirmed rather than assumed. - Whether we had any signal at all. If a customer's ARM deployment failed and nobody was notified, that is a separate and arguably worse defect than the invalid action: a silent onboarding failure with no telemetry. 76 days is a long time for a broken onboarding path to go unreported, which is itself weak evidence that either very few customers used it or that failures were invisible to us. Both readings are worth knowing about.
- Whether re-running the corrected template is idempotent for anyone left partially deployed.
Correction carried forward
The
assignableScopesconcern originally raised in this issue was wrong and has been retracted in a separate comment. LeanerCloud/cloud-commitments-cli#1658 had already fixed it. The invalid-action finding is unaffected and independently confirmed against Azure's live operation catalog.claimed by cc-platform-w3
Found while root-causing LeanerCloud/cloud-commitments-cli#1794. The code fix ships with that issue's PR; this issue is about the operational fallout the code fix does not address.
What happened
arm/CUDly-CrossSubscription/template.json:51containedMicrosoft.Capacity/reservationOrders/purchase/action, which does not exist in Azure's operation catalog. Azure rejects an entireMicrosoft.Authorization/roleDefinitionsresource that references an unknown action withInvalidActionOrNotAction(400).Verified against the live catalog and reproduced end to end on the Terraform twin of this role: the deployment fails, and removing the single action makes it succeed. See LeanerCloud/cloud-commitments-cli#1794 for the full evidence.
Why this needs its own issue
That ARM template is customer-facing. Customers deploy it to grant CUDly cross-subscription access during onboarding. Every customer who ran it should have hit the same 400.
The fix corrects the template going forward. It does not tell us:
git log -Sto date its introduction, which bounds the affected window.dependsOnthe role definition. Those should not have been created, but ARM partial-deployment behaviour should be confirmed rather than assumed, especially given the template declares an assignment at/providers/Microsoft.Capacityscope.Also worth checking while in here
The ARM template's
assignableScopesincludes/providers/Microsoft.Capacity, but the Terraform module deliberately removed that scope, documenting that a subscription-scoped principal cannot register an assignable scope above its own subscription and that including it makes the role-definition write 403 at the higher scope.So ARM and Terraform have diverged on assignable scopes, and the ARM side carries the scope the TF side documented as unusable. The role-parity checker did not flag this. That may be a second latent onboarding failure, independent of the invalid action, and it should be tested rather than reasoned about.
Verification
Deploy the corrected template into a scratch subscription and confirm the role definition and both role assignments are created. Do this specifically for the
/providers/Microsoft.Capacity-scoped assignment, which is the one most likely to still fail after the action fix.