fix(hubs): assign requested storage roles to apps that don't create storage (#2253) - #2262
Open
Roland Krummenacher (RolandKrummenacher) wants to merge 1 commit into
Conversation
…torage Managed exports never created any Cost Management exports. Every export creation failed with an Unauthorized error because Cost Management needs the caller to write role assignments on the destination storage account (exports are created with a system-assigned identity that must be granted access to the destination). The Managed Exports app requests the Role Based Access Control Administrator role via the storageRoles parameter of fx/hub-app.bicep, but the role assignment loop was gated on the "Storage" feature. Apps only declare that feature when they create the publisher storage account, and Managed Exports uses the account created by Microsoft.CostManagement.Exports, so every role it requested was silently dropped at build time. Roles requested via storageRoles are now assigned whenever the app uses Data Factory, since the storage account is shared across all apps from the same publisher. The base data management roles (Storage Account Contributor, Storage Blob Data Contributor, Reader) stay tied to the "Storage" feature, so no other app changes. Also corrects the user-facing role name, which has referred to the removed User Access Administrator role since it was replaced by RBAC Administrator in v13. Fixes #2253 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Roland Krummenacher (RolandKrummenacher)
requested review from
Brett Wilson (MSBrett) and
Michael Flanakin (flanakin)
as code owners
August 17, 2026 07:05
Copilot started reviewing on behalf of
Roland Krummenacher (RolandKrummenacher)
August 17, 2026 07:06
View session
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a FinOps hubs deployment regression where Managed Exports requested a storage RBAC role (RBAC Administrator) but the hub-app module never created the corresponding role assignment, causing Cost Management export creation to fail with Unauthorized. It updates the hub app role-assignment gating so requested storageRoles are applied whenever the app uses Data Factory (even if it doesn’t create the publisher storage account), and aligns user-facing documentation/tooling text to the correct role name.
Changes:
- Update
fx/hub-app.bicepso roles requested viastorageRolesare included in the storage role assignment loop wheneverusesDataFactoryis true, while keeping the base data-management roles tied to theStoragefeature. - Correct “User Access Administrator” wording to “Role Based Access Control Administrator” across the hub template parameter descriptions and portal UI tooltip.
- Add a Pester regression test suite to validate the role-assignment gating and the Managed Exports role request.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/templates/finops-hub/modules/hub.bicep | Updates the managed exports parameter description to reference RBAC Administrator (not User Access Administrator). |
| src/templates/finops-hub/modules/fx/hub-app.bicep | Fixes storage role assignment gating so storageRoles flow into assignments when using Data Factory, even without the Storage feature. |
| src/templates/finops-hub/main.bicep | Updates the managed exports parameter description to reference RBAC Administrator. |
| src/templates/finops-hub/createUiDefinition.json | Updates the portal UI tooltip text for managed exports to reference RBAC Administrator and clarify the requirement. |
| src/powershell/Tests/Unit/HubsAppStorageRoles.Tests.ps1 | Adds regression tests to ensure storageRoles aren’t gated on Storage and that Managed Exports requests RBAC Administrator. |
| docs-mslearn/toolkit/hubs/template.md | Updates docs role table + parameter docs to reference RBAC Administrator and refreshes ms.date. |
| docs-mslearn/toolkit/changelog.md | Adds a changelog entry for the fix and refreshes ms.date. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🛠️ Description
Fixes #2253.
Managed exports never created any Cost Management exports.
config_ConfigureExportsreached the export creation activity and failed with:Cost Management creates exports with a system-assigned identity and must grant that identity access to the destination, so the caller needs to write role assignments on the destination storage account. The Managed Exports app requests Role Based Access Control Administrator for the Data Factory identity via the
storageRolesparameter offx/hub-app.bicep, but the assignment was never created.Root cause
The storage role assignment loop was gated on the
Storagefeature:Apps only declare
Storagewhen they create the publisher storage account. Managed Exports uses the account created byMicrosoft.CostManagement.Exports, sousesStorageisfalseand every role it passed was silently dropped at build time. It is the only app that passesstorageRoles, so it was the only one affected.This regressed in the app modularization (#1800): in v12 the same role was coupled directly to
enableManagedExportsand assigned with no feature condition.Fix
Roles requested via
storageRolesare assigned whenever the app uses Data Factory, since the storage account is shared across all apps from the same publisher. The base data management roles (Storage Account Contributor, Storage Blob Data Contributor, Reader) stay tied to theStoragefeature, so nothing changes for any other app.Why not declare the
Storagefeature on Managed Exports (the fix suggested in the issue)?usesStoragedoesn't only gate role assignments — it also deploys the storage account, both private endpoints, the private DNS zone groups, the ADF managed private endpoint, the storage linked service, and theGet/ApproveStoragePrivateEndpointConnectionsnested deployments. Adding the feature to Managed Exports would duplicate all of that against a storage account another app already owns, and under private routingMicrosoft.CostManagement.ManagedExportshas nodependsOnsequencing againstMicrosoft.FinOpsHubs.Analytics, so the two would race on identically named private endpoint resources and nested deployments.Fixing the gate instead keeps the blast radius to the one missing role assignment and also fixes the class of bug for any future app that requests storage roles.
Docs
Item 1 from the issue: the role table and the
enableManagedExportstooltip/description still named User Access Administrator, which hasn't appeared anywhere in the template since v13 replaced it with RBAC Administrator. Corrected intemplate.md,main.bicep,hub.bicep, andcreateUiDefinition.json.Item 2 from the issue (
Save Scopesfailing when the settings file has exactly one scope) is not addressed here — it is non-blocking, the recovery activity already handles it, and the real fix belongs in the PowerShell that serializessettings.json. Happy to file it separately.🧪 Testing
bicep build src/templates/finops-hub/main.bicep— clean (only pre-existingno-unused-paramswarnings inRecommendations/app.bicep).Microsoft.CostManagement.ManagedExports_Registernow emits a role assignment forf58310d9-a9f6-439a-9e8d-f62e7b41a168on the storage account, and thatfactoryStorageRolesis unchanged for every other app.src/powershell/Tests/Unit/HubsAppStorageRoles.Tests.ps1regression suite (6 tests): the assignment loop isn't gated onusesStorage,storageRolesalways flows into it, every app requesting storage roles declaresDataFactory, and Managed Exports still requests RBAC Administrator. Confirmed the suite fails against the pre-fix condition.Tests/Unit(2379 passed, 4 skipped) andTests/Lint(3587 passed) suites pass.Not yet validated with a live hub deployment — worth a deploy test with
enableManagedExports = truebefore merge.📋 Checklist
Reviewer tasks
Storagefeature)Contributor tasks
🤖 Generated with Claude Code