Skip to content

fix(iac/azure): custom role grants Microsoft.Capacity/reservationOrders/purchase/action#744

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/731-azure-capacity-purchase-action
May 27, 2026
Merged

fix(iac/azure): custom role grants Microsoft.Capacity/reservationOrders/purchase/action#744
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/731-azure-capacity-purchase-action

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 27, 2026

Why this fix is needed

Live reservation purchases on production Azure subscriptions are failing with 403:

"The client ... does not have authorization to perform action 'Microsoft.Capacity/reservationOrders/purchase/action' over scope '/providers/Microsoft.Capacity/reservationOrders/<orderId>'"

The two-step calculatePrice -> purchase flow introduced by PR #680 (providers/azure/services/internal/reservations/purchase.go) requires Microsoft.Capacity/reservationOrders/purchase/action. The built-in Reservation Purchaser role (currently the only role our IaC assigns) does not include that action; it stops at reservationOrders/write + reservationOrders/read + calculatePrice/action. A custom role with the explicit actions is the tightest fix.

Changes

ARM template (arm/CUDly-CrossSubscription/template.json):

Before: two roleAssignments using the built-in Reservation Purchaser role (one at subscription scope, one at /providers/Microsoft.Capacity scope).

After:

  • Added Microsoft.Authorization/roleDefinitions resource (apiVersion: 2022-04-01) — roleName: "CUDly Reservation Purchaser (custom)" with all 11 required actions (see below), assignableScopes includes both subscription ID and /providers/Microsoft.Capacity.
  • Replaced both Reservation Purchaser assignments with assignments of the custom role; both carry dependsOn pointing to the roleDefinition resource so ARM provisions them in the correct order.
  • Removed the built-in Reservation Purchaser from variables and all assignments.
  • Reader and CostManagementReader assignments are untouched.

Terraform (iac/federation/azure-target/terraform/main.tf):

Before: azurerm_role_assignment.cudly_reservations with role_definition_name = "Reservation Purchaser".

After:

  • Added azurerm_role_definition.cudly_reservation_purchaser with the same 11 actions, scoped to data.azurerm_subscription.current.id, assignable_scopes includes both subscription ID and /providers/Microsoft.Capacity.
  • Modified azurerm_role_assignment.cudly_reservations to reference role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id.
  • Added depends_on = [azurerm_role_definition.cudly_reservation_purchaser] per the project's RBAC propagation lesson (Azure takes up to 10 min; without this the assignment can 403 with RoleDefinitionDoesNotExist on the first apply).

Actions granted by the custom role (both bundles):

Microsoft.Capacity/register/action
Microsoft.Capacity/calculatePrice/action
Microsoft.Capacity/catalogs/read
Microsoft.Capacity/reservationOrders/read
Microsoft.Capacity/reservationOrders/write
Microsoft.Capacity/reservationOrders/purchase/action   <- the missing one
Microsoft.Capacity/reservationOrders/reservations/read
Microsoft.BillingBenefits/savingsPlanOrderAliases/write
Microsoft.BillingBenefits/savingsPlanOrders/read
Microsoft.BillingBenefits/savingsPlanOrders/savingsPlans/read
Microsoft.BillingBenefits/savingsPlanOrders/action

Test plan

  • Re-deploy the ARM bundle (az deployment sub create) to the affected subscription.
  • Confirm the custom role definition appears in Azure Portal under the subscription's IAM / Role definitions.
  • Confirm two role assignments for the CUDly SP exist (subscription scope + /providers/Microsoft.Capacity scope).
  • Retry the failed reservation approval in CUDly — should succeed without 403.
  • Run terraform apply on a test subscription's azure-target stack; verify no error and role assignment references the custom role.

Closes #731

Summary by CodeRabbit

  • Chores
    • Updated role-based access control configuration to use custom role definitions with granular permission restrictions, replacing broader built-in roles.

Review Change Stack

…rs/purchase/action

The built-in Reservation Purchaser role lacks
Microsoft.Capacity/reservationOrders/purchase/action, which the two-step
calculatePrice -> purchase flow (PR #680) requires. Replace it with a
custom role that explicitly enumerates the runtime actions used by the
reservation + savings-plan purchase paths, assignable at subscription
scope AND /providers/Microsoft.Capacity.

Fixes 403 AuthorizationFailed on live reservation purchases for newly
onboarded Azure subscriptions.

Closes #731 (if open).
@cristim cristim added triaged Item has been triaged priority/p0 Drop everything; same-day fix severity/high Significant harm urgency/now Drop other things impact/many Affects most users effort/s Hours type/bug Defect labels May 27, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 14 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c272c3e-8282-4756-b479-3ae212b1d89f

📥 Commits

Reviewing files that changed from the base of the PR and between e47429e and bd861f8.

📒 Files selected for processing (6)
  • iac/federation/azure-target/terraform/main.tf
  • terraform/modules/compute/azure/container-apps/main.tf
  • terraform/modules/iam/azure/cudly-reservation-role/.terraform.lock.hcl
  • terraform/modules/iam/azure/cudly-reservation-role/main.tf
  • terraform/modules/iam/azure/cudly-reservation-role/outputs.tf
  • terraform/modules/iam/azure/cudly-reservation-role/variables.tf
📝 Walkthrough

Walkthrough

Both ARM template and Terraform configurations replace the built-in Reservation Purchaser role with a custom role that explicitly grants the required Microsoft.Capacity and Microsoft.BillingBenefits actions for the CUDly purchase and exchange flows, fixing authorization failures when executing reservation orders.

Changes

Custom RBAC role for purchase authorization

Layer / File(s) Summary
ARM template custom role definition
arm/CUDly-CrossSubscription/template.json
variables.roles removes the reservationPurchaser built-in role reference. Custom role definition is updated with new description, explicit actions for reservation/billing benefits operations (calculatePrice, calculateExchange, exchange, purchase, read, write, validate), and assignable scopes expanded to include subscription and /providers/Microsoft.Capacity provider scope.
ARM template role assignment wiring
arm/CUDly-CrossSubscription/template.json
Custom role is assigned to the CUDly service principal at both subscription scope and /providers/Microsoft.Capacity scope. Reader and costManagementReader role assignments receive updated descriptions. Role assignments for the removed built-in reservationPurchaser role are eliminated.
Terraform custom role definition and assignment
iac/federation/azure-target/terraform/main.tf
New custom role definition (azurerm_role_definition.cudly_reservation_purchaser) with tightly scoped permissions for reservation order and billing benefits operations. Role assignment for the cudly service principal is updated to use the custom role via role_definition_id instead of the Reservation Purchaser role name, with explicit depends_on for safe role propagation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • LeanerCloud/CUDly#732: Updates the same ARM template RBAC roleDefinitions/roleAssignments to add a custom role with concrete reservation and savings-plan purchase actions.

Suggested labels

severity/critical

Poem

🐰 Hops through the clouds with a purchase to make,
But roles that mislead make operations break!
Custom permissions now crystal and clear,
Reservation orders? No more 403 fear!
Azure and Terraform dance in accord,
Action by action, authorization restored!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the main change: introducing a custom role that grants the critical Microsoft.Capacity/reservationOrders/purchase/action permission needed to fix failing Azure reservation purchases.
Linked Issues check ✅ Passed The code changes fully address all requirements from issue #731: custom role with 11 actions covers all documented requirements (reservations, exchange, savings plans, billing benefits); role is assignable at subscription scope; version metadata included; ARM template and Terraform updated; built-in Reservation Purchaser retained for recommendation reads.
Out of Scope Changes check ✅ Passed All changes in both files (ARM template and Terraform) are directly focused on replacing the insufficient built-in role with a custom role granting necessary purchase actions; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/731-azure-capacity-purchase-action

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@arm/CUDly-CrossSubscription/template.json`:
- Around line 88-95: The role assignment `name` GUID at provider scope is not
unique per subscription and can collide across deployments; update the `name`
expression for the Microsoft.Authorization/roleAssignments resource so it
includes the subscription identifier (e.g., subscription().subscriptionId) along
with parameters('servicePrincipalObjectId') and the fixed salt
'cudlyCustomRoleCapacity' to produce a subscription-scoped GUID; locate the
resource that sets "name": "[guid(parameters('servicePrincipalObjectId'),
'cudlyCustomRoleCapacity')]" and replace the GUID inputs to include the
subscription id so each subscription gets a distinct roleAssignment name while
keeping the same salt and principalId/roleDefinitionId usage.
- Around line 45-56: The custom role's "actions" array is missing required RBAC
actions which cause 403s for exchange and savings-plan flows; update the actions
list (the JSON property "actions" in the role definition) to include
Microsoft.Capacity/calculateExchange/action, Microsoft.Capacity/exchange/action,
Microsoft.BillingBenefits/savingsPlanOrders/write, and
Microsoft.BillingBenefits/validate/action so the role covers exchange and
savings-plan operations.

In `@iac/federation/azure-target/terraform/main.tf`:
- Around line 97-101: Add a second role assignment at the Microsoft.Capacity
provider scope so the service principal can call provider-root endpoints: create
a new azurerm_role_assignment (e.g.,
azurerm_role_assignment.cudly_reservation_purchaser_provider_scope) with scope
set to "/subscriptions/${local.subscription_id}/providers/Microsoft.Capacity",
role_definition_id set to
azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id,
principal_id set to azuread_service_principal.cudly.object_id, and ensure it
depends_on azurerm_role_definition.cudly_reservation_purchaser (and any related
resources) just like the existing subscription-scoped assignment.
- Around line 71-82: The actions list in the Terraform RBAC assignment is
missing required permissions for exchange and savings-plan flows; update the
actions array (the "actions" attribute in the relevant IAM role/resource block)
to include Microsoft.Capacity/calculateExchange/action,
Microsoft.Capacity/exchange/action,
Microsoft.BillingBenefits/savingsPlanOrders/write, and
Microsoft.BillingBenefits/validate/action so tenants can perform exchange and
savings-plan validation/purchase operations; locate the actions array shown in
the diff and append these four action strings to it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2d9b7b8-7170-46a5-a98e-bcb315726467

📥 Commits

Reviewing files that changed from the base of the PR and between 9321a59 and e47429e.

📒 Files selected for processing (2)
  • arm/CUDly-CrossSubscription/template.json
  • iac/federation/azure-target/terraform/main.tf

Comment on lines 45 to 56
"actions": [
"Microsoft.Capacity/calculateprice/action",
"Microsoft.Capacity/reservationorders/write",
"Microsoft.Capacity/reservationorders/read",
"Microsoft.Capacity/reservationorders/reservations/read",
"Microsoft.Capacity/register/action",
"Microsoft.Capacity/calculatePrice/action",
"Microsoft.Capacity/catalogs/read",
"Microsoft.Capacity/reservationOrders/read",
"Microsoft.Capacity/reservationOrders/write",
"Microsoft.Capacity/reservationOrders/purchase/action",
"Microsoft.Capacity/reservationOrders/reservations/read",
"Microsoft.BillingBenefits/savingsPlanOrderAliases/write",
"Microsoft.BillingBenefits/savingsPlanOrders/read",
"Microsoft.BillingBenefits/savingsPlanOrders/savingsPlans/read",
"Microsoft.BillingBenefits/savingsPlanOrders/action"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

template="arm/CUDly-CrossSubscription/template.json"

echo "Current custom-role actions:"
jq -r '
  .resources[]
  | select(.type=="Microsoft.Authorization/roleDefinitions")
  | .properties.permissions[0].actions[]
' "$template" | sort

echo
for action in \
  "Microsoft.Capacity/calculateExchange/action" \
  "Microsoft.Capacity/exchange/action" \
  "Microsoft.BillingBenefits/savingsPlanOrders/write" \
  "Microsoft.BillingBenefits/validate/action"
do
  if jq -e --arg a "$action" '
    .resources[]
    | select(.type=="Microsoft.Authorization/roleDefinitions")
    | .properties.permissions[0].actions
    | index($a)
  ' "$template" >/dev/null; then
    echo "FOUND   $action"
  else
    echo "MISSING $action"
  fi
done

Repository: LeanerCloud/CUDly

Length of output: 814


Add the missing RBAC actions to the custom role permissions
The custom role action list in arm/CUDly-CrossSubscription/template.json omits these required actions, so exchange and some savings-plan flows can still fail with 403:

  • Microsoft.Capacity/calculateExchange/action
  • Microsoft.Capacity/exchange/action
  • Microsoft.BillingBenefits/savingsPlanOrders/write
  • Microsoft.BillingBenefits/validate/action
Suggested patch
             "actions": [
               "Microsoft.Capacity/register/action",
               "Microsoft.Capacity/calculatePrice/action",
+              "Microsoft.Capacity/calculateExchange/action",
               "Microsoft.Capacity/catalogs/read",
               "Microsoft.Capacity/reservationOrders/read",
               "Microsoft.Capacity/reservationOrders/write",
               "Microsoft.Capacity/reservationOrders/purchase/action",
+              "Microsoft.Capacity/exchange/action",
               "Microsoft.Capacity/reservationOrders/reservations/read",
               "Microsoft.BillingBenefits/savingsPlanOrderAliases/write",
               "Microsoft.BillingBenefits/savingsPlanOrders/read",
+              "Microsoft.BillingBenefits/savingsPlanOrders/write",
               "Microsoft.BillingBenefits/savingsPlanOrders/savingsPlans/read",
-              "Microsoft.BillingBenefits/savingsPlanOrders/action"
+              "Microsoft.BillingBenefits/savingsPlanOrders/action",
+              "Microsoft.BillingBenefits/validate/action"
             ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arm/CUDly-CrossSubscription/template.json` around lines 45 - 56, The custom
role's "actions" array is missing required RBAC actions which cause 403s for
exchange and savings-plan flows; update the actions list (the JSON property
"actions" in the role definition) to include
Microsoft.Capacity/calculateExchange/action, Microsoft.Capacity/exchange/action,
Microsoft.BillingBenefits/savingsPlanOrders/write, and
Microsoft.BillingBenefits/validate/action so the role covers exchange and
savings-plan operations.

Comment on lines +88 to 95
"scope": "/providers/Microsoft.Capacity",
"name": "[guid(parameters('servicePrincipalObjectId'), 'cudlyCustomRoleCapacity')]",
"dependsOn": [
"[variables('customRoleDefinitionId')]"
],
"properties": {
"roleDefinitionId": "[variables('roles').costManagementReader]",
"roleDefinitionId": "[variables('customRoleDefinitionId')]",
"principalId": "[parameters('servicePrincipalObjectId')]",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Azure RBAC ARM templates, are Microsoft.Authorization/roleAssignmentsnames unique within their assignment scope, and would reusing the same name at tenant scope/providers/Microsoft.Capacitywith a differentroleDefinitionId across subscriptions cause a conflict or immutable update failure?

💡 Result:

In Azure, Microsoft.Authorization/roleAssignments names must be a globally unique identifier (GUID) [1][2][3]. Crucially, these names must be unique within the Microsoft Entra tenant, regardless of the scope at which the role assignment is applied [1][2][4]. Regarding your scenario: 1. Conflict Potential: If you attempt to use the same GUID (name) for two different role assignments, Azure will interpret this as an update to the existing assignment rather than the creation of a new, separate one [5]. If the properties of the new assignment (e.g., roleDefinitionId, principalId, or scope) differ from the existing one, the deployment will likely fail or cause an immutable update error, depending on the specifics of the change [1][5][6]. 2. Immutable Update Failure: Because a role assignment name is the identifier used to track the resource, changing its core properties while keeping the same name essentially asks Azure to modify an existing resource [5]. Since role assignments are tracked by name across the tenant, you cannot have multiple distinct assignments share the same name [1][2]. To avoid conflicts and ensure idempotency in your ARM templates or Bicep files, it is best practice to generate a deterministic GUID based on the unique combination of the scope, principal ID, and role definition ID (for example, by using the guid function in Bicep or ARM) [4][5][7]. This ensures that for a specific set of parameters, the same assignment name is always generated, preventing accidental duplicates while ensuring that unrelated assignments do not collide [4][7].

Citations:


Make the provider-scope role assignment name unique per subscription

Microsoft.Authorization/roleAssignments uses the ARM name (GUID) as the Entra tenant identifier for the role assignment; reusing guid(parameters('servicePrincipalObjectId'), 'cudlyCustomRoleCapacity') at scope /providers/Microsoft.Capacity while roleDefinitionId varies per subscription can cause later deployments to target/update the same roleAssignment name and fail (immutable update/resource identifier mismatch).

Suggested patch
-      "name": "[guid(parameters('servicePrincipalObjectId'), 'cudlyCustomRoleCapacity')]",
+      "name": "[guid(parameters('servicePrincipalObjectId'), 'cudlyCustomRoleCapacity', subscription().subscriptionId)]",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arm/CUDly-CrossSubscription/template.json` around lines 88 - 95, The role
assignment `name` GUID at provider scope is not unique per subscription and can
collide across deployments; update the `name` expression for the
Microsoft.Authorization/roleAssignments resource so it includes the subscription
identifier (e.g., subscription().subscriptionId) along with
parameters('servicePrincipalObjectId') and the fixed salt
'cudlyCustomRoleCapacity' to produce a subscription-scoped GUID; locate the
resource that sets "name": "[guid(parameters('servicePrincipalObjectId'),
'cudlyCustomRoleCapacity')]" and replace the GUID inputs to include the
subscription id so each subscription gets a distinct roleAssignment name while
keeping the same salt and principalId/roleDefinitionId usage.

Comment thread iac/federation/azure-target/terraform/main.tf Outdated
Comment on lines +97 to +101
scope = "/subscriptions/${local.subscription_id}"
role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
principal_id = azuread_service_principal.cudly.object_id

depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add the provider-scope assignment for Microsoft.Capacity.

This only assigns the role at /subscriptions/${local.subscription_id}, but the reservation flow calls provider-root endpoints under /providers/Microsoft.Capacity/.... Without a second assignment at that provider scope, the original 403 on calculatePrice/purchase can still reproduce for Terraform-managed tenants.

Suggested diff
 resource "azurerm_role_assignment" "cudly_reservations" {
   scope              = "/subscriptions/${local.subscription_id}"
   role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
   principal_id       = azuread_service_principal.cudly.object_id

   depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
 }
+
+resource "azurerm_role_assignment" "cudly_reservations_capacity_provider" {
+  scope              = "/providers/Microsoft.Capacity"
+  role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
+  principal_id       = azuread_service_principal.cudly.object_id
+
+  depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scope = "/subscriptions/${local.subscription_id}"
role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
principal_id = azuread_service_principal.cudly.object_id
depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
resource "azurerm_role_assignment" "cudly_reservations" {
scope = "/subscriptions/${local.subscription_id}"
role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
principal_id = azuread_service_principal.cudly.object_id
depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
}
resource "azurerm_role_assignment" "cudly_reservations_capacity_provider" {
scope = "/providers/Microsoft.Capacity"
role_definition_id = azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id
principal_id = azuread_service_principal.cudly.object_id
depends_on = [azurerm_role_definition.cudly_reservation_purchaser]
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iac/federation/azure-target/terraform/main.tf` around lines 97 - 101, Add a
second role assignment at the Microsoft.Capacity provider scope so the service
principal can call provider-root endpoints: create a new azurerm_role_assignment
(e.g., azurerm_role_assignment.cudly_reservation_purchaser_provider_scope) with
scope set to
"/subscriptions/${local.subscription_id}/providers/Microsoft.Capacity",
role_definition_id set to
azurerm_role_definition.cudly_reservation_purchaser.role_definition_resource_id,
principal_id set to azuread_service_principal.cudly.object_id, and ensure it
depends_on azurerm_role_definition.cudly_reservation_purchaser (and any related
resources) just like the existing subscription-scoped assignment.

… add Reader

The host container-app identity was still on the built-in Reservation
Purchaser role (which omits reservationOrders/purchase/action, the same
bug this PR fixes customer-side). Adds the same custom role plus a
subscription-scope Reader, factoring the role definition into a shared
module so customer + host stay in lockstep.

- New module: terraform/modules/iam/azure/cudly-reservation-role
  (main.tf / variables.tf / outputs.tf) - single source of truth for
  the custom role's action list
- iac/federation/azure-target/terraform/main.tf: switch from inlined
  azurerm_role_definition to the shared module (no behavioural change)
- terraform/modules/compute/azure/container-apps/main.tf: replace
  built-in Reservation Purchaser assignment with custom-role module +
  add subscription-scope Reader for host-account resource enumeration

Refs #731.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

effort/s Hours impact/many Affects most users priority/p0 Drop everything; same-day fix severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/now Drop other things

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant