Skip to content

Azure ARM template grants role missing purchase/calculatePrice actions -- all reservation purchases 403 #731

Description

@cristim

Symptom

User-reported purchase failure (Azure two-step POST .../reservationOrders/{id}/purchase):

some purchases failed: [Standard_D2d_v4: purchase failed: reservation purchase failed with status 403:
{"error":{"code":"AuthorizationFailed",
"message":"The client '0c2390de-60fd-4df3-bb60-3e6a8b6821e4'
with object id '8158bcef-a8f2-4aca-8d16-b513a57d2214'
does not have authorization to perform action
'Microsoft.Capacity/reservationOrders/purchase/action'
over scope '/providers/Microsoft.Capacity/reservationOrders/5205aaa2-18e3-44ec-bebb-f3e9df66d2e4'
or the scope is invalid. If access was recently granted, please refresh your credentials."}}]

Root cause

arm/CUDly-CrossSubscription/template.json grants the CUDly SP the built-in role "Reservation Purchaser" (f7b75c60-3036-4b75-91c3-6b41c27c1689) at both subscription scope and /providers/Microsoft.Capacity tenant scope.

Verified via az role definition list --name "Reservation Purchaser", that role's permissions[0].actions is:

Microsoft.Authorization/roleAssignments/read
Microsoft.Capacity/catalogs/read
Microsoft.Capacity/register/action
Microsoft.Compute/register/action
Microsoft.Consumption/register/action
Microsoft.Consumption/reservationRecommendationDetails/read
Microsoft.Consumption/reservationRecommendations/read
Microsoft.Resources/subscriptions/read
Microsoft.Resources/subscriptions/resourceGroups/read
Microsoft.SQL/register/action
Microsoft.Support/supporttickets/write

The name is misleading. The role only lets the SP read recommendations and register the provider. None of the actions actually required to execute a two-step purchase are present:

  • Microsoft.Capacity/calculatePrice/action (step 1 of DoPurchaseTwoStep)
  • Microsoft.Capacity/reservationOrders/read
  • Microsoft.Capacity/reservationOrders/write
  • Microsoft.Capacity/reservationOrders/purchase/action (step 2 of DoPurchaseTwoStep, the one that 403's)

Same shape for savings plans: Savings plan Purchaser includes Microsoft.BillingBenefits/savingsPlanOrders/write but is missing the validate/action used by AzureSPProber.ValidatePurchase (PR #724) and likely other narrower actions.

Fix direction

Define a custom role in arm/CUDly-CrossSubscription/template.json (subscription-scoped via Microsoft.Authorization/roleDefinitions) that explicitly enumerates the actions the CUDly executors actually call, then assign that custom role plus the built-in Reservation Purchaser (still needed for reservationRecommendations reads).

Minimum custom action set (verify each by tracing the SDK calls in providers/azure/services/internal/reservations/purchase.go, the seven executors in providers/azure/services/{cache,compute,cosmosdb,database,managedredis,search,synapse}/client.go, the exchange flow in providers/azure/services/compute/exchange.go, and the SP probe in internal/commitmentopts/probe_azure.go):

Reservations:

  • Microsoft.Capacity/calculatePrice/action
  • Microsoft.Capacity/reservationOrders/read
  • Microsoft.Capacity/reservationOrders/write
  • Microsoft.Capacity/reservationOrders/purchase/action
  • Microsoft.Capacity/calculateExchange/action (compute exchange)
  • Microsoft.Capacity/exchange/action (compute exchange)

Savings plans / billing benefits:

The custom role's assignableScopes should be [/subscriptions/{subscriptionId}] so it's tenant-portable. Tag the role with version metadata so future bumps are visible in role assignment history.

Keep the existing built-in Reservation Purchaser assignment — it carries Microsoft.Consumption/reservationRecommendations/read which we still need for recommendation discovery, and removing it would be a regression for users already in production.

Tests required

  • ARM template lint passes (az deployment sub validate against a sandbox subscription).
  • A new acceptance/integration test (or doc verifying manual repro) confirms that with only the new ARM-granted set of roles, a purchase succeeds end-to-end.
  • known-issues.md entry updated to flag the upgrade requirement for existing CUDly tenants (they MUST redeploy the ARM template to refresh their role grants).

Impact

P0 — this is the blocker for any Azure reservation purchase across all seven executors. The error has been hit in production right now. Mark priority/p0, severity/critical. Users with the old ARM template will see this 403 on every Azure purchase.

Source of finding

User report on the live Standard_D2d_v4 purchase attempt + verification of the role's actions array via az role definition list --name "Reservation Purchaser".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions