Skip to content

Reference

Permissions

Two built-in Azure roles, at a scope you pick. Neither can write anything, and one of them carries a permission Infralign never calls.

Infralign’s default Cost API lane runs on exactly two Azure built-in roles, granted to a service principal in your own tenant, at management group scope or per subscription.

The roles grant no workload or resource-configuration write permissions. No Owner or Contributor role is needed, at any scope or stage. That includes engagements, where the architect prepares each change and your team merges it through your own pipeline.

RoleScopeCovered operationsWhy it’s needed
Cost Management ReaderThe management group covering your estate, or each subscription you includeMicrosoft.CostManagement/generateCostDetailsReport (an action), the cost query used to validate it, and Microsoft.Support/*The daily cost report is a POST action, not a read: plain Reader returns 403 on it. The Microsoft-managed definition also permits support-ticket operations. See the support-ticket caveat. Report blobs download over Cost-Management-signed SAS links, so no storage role is involved.
ReaderThe management group covering your estate, or each subscription you include*/read: Resource Graph inventory, Activity Log, Monitor metrics, Advisor recommendationsResource metadata, change history, CPU utilisation, and Advisor signals. Never data inside resources.

These are the default operational roles, not a claim that every action in their Microsoft-managed definitions is least privilege for Infralign. Export and review the effective role definitions rather than relying on their display names.

Roles and scope are this page. The endpoint-level view, naming each API, its api-version, and what breaks without it, lives in data sources and is maintained only there.

Granting either role needs Microsoft.Authorization/roleAssignments/write at the scope you choose, which means Owner or User Access Administrator there. That is a different permission from the one that lets you register the app, and holding one does not imply the other. Entra app registration opens with the 30-second check for it.

The app registration, client secret, and assignment steps are in Entra app registration. The setup wizard checks both roles on every subscription before it stores anything; the equivalent az CLI runbook is there for a change record.

The scope is yours to choose. Neither role grants any write permission at either scope, and Cost Management Reader sits below the billing account in both cases.

Management group scope is the recommendation: one assignment per role on the management group covering your estate, rather than two per subscription. Twenty subscriptions is two assignments instead of forty, and a subscription created later inherits both, so a new subscription needs no portal work. The setup wizard, the Entra app registration walkthrough and the validation check all assume it. The check reads role assignments with $filter=atScope(), which returns management-group-inherited assignments. An inherited grant therefore validates exactly like a direct one.

Per-subscription assignment is the alternative where you do not administer a management group, or where you want the assignment list itself to be the record of which subscriptions are in scope. Start with one and add others when you’re ready.

Neither choice changes what Infralign processes. That is the subscription list you tick in the wizard’s Scope step, and it is independent of where the roles were granted. A management-group assignment does not add a subscription to your scope on its own.

  • Neither role can create, modify, or delete workloads, resource configuration, tags, or exports. The one exception to any broad “read-only” description is covered in the support-ticket caveat.
  • Reader exposes control-plane metadata only and has no DataActions: it cannot read database rows, blob contents, VM disks, application data, or Key Vault secret values.
  • The service principal needs no Microsoft Graph API permissions and no admin consent, only these IAM role assignments. (The separate Infralign sign-in application is what your tenant grants a one-time admin consent to. See Dashboard sign-in.)
  • No agents or extensions are installed on your VMs, and no standing production privilege exists. See the safety model.

Security teams reasonably ask whether Reader is too broad.

Reader’s */read is control plane only: resource existence, SKUs, tags, and configuration metadata. It has no DataActions, so it cannot read data planes: no blob contents, Key Vault secret values, database rows, or VM disks. It also cannot call action-style operations such as listKeys. Control-plane metadata can still contain sensitive names, tags, and configuration, so review its scope as customer data.

Log Analytics is the exception, and there “no DataActions” does less work than it appears to. Query results come back from Microsoft.OperationalInsights/workspaces/query/read, which is a control-plane read, so any role holding */read across that provider can reach log content without holding a single data action. Built-in Reader holds it, and so does the custom role below.

Infralign does not query Log Analytics. The nightly run reads four providers: Cost Management, Resource Graph, Advisor, and Azure Monitor metrics (Microsoft.Insights/metrics), which is what sizes an oversized VM or an idle disk from its own utilisation rather than from its price. Monitor metrics and Monitor logs are different providers, and only the first is used.

The grant is still real, so if your workspaces carry application logs you treat as sensitive, assign at a scope that excludes them. That is the control, and it is yours rather than ours.

The platform asks for that breadth because of Azure Resource Graph’s authorisation model: Microsoft.ResourceGraph/resources/read alone returns only the resources the identity can already read individually. A narrower role does not make queries fail. It makes resources silently vanish from inventory, so a blind spot reads as “no waste found” rather than “access denied”. A complete custom role therefore converges back towards */read, which is Reader. (Monitoring Reader is not a narrower alternative: its definition also includes */read.)

Microsoft’s current Cost Management Reader definition bundles cost and billing operations and Microsoft.Support/*. That wildcard permits the service principal to create and update Azure support tickets. Infralign does not call those operations, but assigning the built-in role grants the capability; the role is therefore not literally read-only. If your organisation cannot accept an unused permission, agree and validate the custom-role alternative below before connecting.

Least-privilege custom role (strict tenants)

Section titled “Least-privilege custom role (strict tenants)”

For security-sensitive tenants, use a combined custom role that includes the cost and inventory reads but omits Microsoft.Support/*. Creating a role definition requires Microsoft.Authorization/roleDefinitions/write: Owner or User Access Administrator on the assignable scope. That is a higher privilege than assigning the built-in roles.

Two of the entries below are action-form, not */read, because the operations they authorise are POSTs. generateCostDetailsReport is what the nightly run calls, and query is what the setup wizard’s cost check calls. A definition built only from */read lines returns 403 on both, which is the same failure plain Reader produces.

{
"Name": "Infralign Estate Reader",
"IsCustom": true,
"Description": "Cost, control-plane inventory, metrics, and advisory access for Infralign, without support-ticket permission.",
"Actions": [
"Microsoft.Consumption/*/read",
"Microsoft.CostManagement/*/read",
"Microsoft.CostManagement/generateCostDetailsReport/action",
"Microsoft.CostManagement/query/action",
"Microsoft.Billing/billingPeriods/read",
"Microsoft.Billing/billingProperty/read",
"Microsoft.Management/managementGroups/read",
"Microsoft.ResourceGraph/resources/read",
"Microsoft.Resources/changes/read",
"Microsoft.Resources/subscriptions/read",
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Insights/metrics/read",
"Microsoft.Insights/eventtypes/values/read",
"Microsoft.Advisor/recommendations/read",
"Microsoft.Advisor/configurations/read",
"Microsoft.Compute/*/read",
"Microsoft.Storage/*/read",
"Microsoft.Sql/*/read",
"Microsoft.Network/*/read",
"Microsoft.Web/*/read",
"Microsoft.ContainerService/*/read",
"Microsoft.DBforPostgreSQL/*/read",
"Microsoft.DocumentDB/*/read",
"Microsoft.OperationalInsights/*/read"
],
"NotActions": [],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/<subscription-id-1>",
"/subscriptions/<subscription-id-2>"
]
}

A custom role can only be assigned inside its AssignableScopes, so list every subscription you intend to analyse, or use a management-group scope such as /providers/Microsoft.Management/managementGroups/<group-id> that contains them.

A reviewer checking this list against data sources will ask about four of the entries:

  • Microsoft.Resources/changes/read is what the Resource Graph change history needs. Reader’s blanket */read covers it; this role has to name it. Without it the change context is missing, and it goes missing the way this page warns about elsewhere: silently, as an empty result rather than an error.
  • Microsoft.Management/managementGroups/read is inert at subscription scope. It earns its place only when the role is assigned at a management group, which is the scope this page recommends.
  • Microsoft.OperationalInsights/*/read is kept at parity with built-in Reader rather than narrowed, so that swapping to this role changes what is granted as little as possible. It does reach Log Analytics query content, which is discussed under what Reader cannot see. Nothing in the nightly run calls it; scope is the control.
  • Microsoft.Capacity/*/read is the one a reviewer expects and will not find. Nothing calls a reservations API. Reservation and savings-plan coverage comes from the amortised cost rows in the daily lane, where a commitment appears as a benefit identifier on the charge. It is a cost-record fact rather than a separate permission.

Extend the provider list to match your estate: every provider you want analysed needs its own */read line. Common additions: Microsoft.KeyVault/*/read, Microsoft.ContainerRegistry/*/read, Microsoft.Cache/*/read, and Microsoft.DBforMySQL/*/read.

Terminal window
# Create the role definition from the JSON above
az role definition create --role-definition @infralign-estate-reader.json
# Assign it to the service principal, per subscription
az role assignment create \
--assignee-object-id <sp-object-id> \
--assignee-principal-type ServicePrincipal \
--role "Infralign Estate Reader" \
--scope /subscriptions/<subscription-id>

Allow a few minutes for the new role definition and its assignments to propagate before validating. That is the same window the role assignment steps call out.

The trade-offs are yours to carry:

  • you own maintaining the list as the estate evolves;
  • any resource type not in the list silently drops out of inventory, utilisation metrics, and orphan detection;
  • cost data still arrives regardless (the custom role’s cost operations cover billing for every resource), so a missed type shows its spend, but without the inventory or metrics context behind it;
  • do not leave Cost Management Reader assigned alongside the validated custom role, or its Microsoft.Support/* capability remains effective.

The simpler lever is scope, not role. You choose where the roles are assigned (the management group covering your estate, or only the subscriptions you name) and, independently of that, which subscriptions you tick in the wizard’s Scope step. A sensitive subscription can therefore be left out of the engagement entirely, whichever assignment scope you use. See scope minimisation.

IntegrationAccessWhy it’s needed
Microsoft TeamsIncoming webhook to a channelLets Infralign post the monthly summary and cost alerts into a channel you nominate.

Teams is optional. The visibility tier and a meaningful audit work from the Azure roles alone. No self-service field exists for it: you send Infralign an incoming webhook, a person wires it up, and nothing is posted to you unless you ask.

Your users sign in separately from the ingestion service principal, using their existing Microsoft work account through the multi-tenant Infralign sign-in application, which a tenant admin consents to once. A per-tenant allowlist bound to your tenant ID controls who can sign in, and passwords are never shared with Infralign.

The consent procedure and troubleshooting are in dashboard sign-in. Your own admins manage the list, in manage who has access.

Revoke access at any time without action from Infralign. Removing an assignment takes effect when Azure applies the RBAC change; deleting the app registration or credential prevents the next authentication.

  1. Remove both role assignments from Access control (IAM) at the scope you granted them. If you granted them on a management group, removing them there revokes every subscription beneath it in one action; if you granted them per subscription, remove them on each. Deleting the app registration revokes everything at once either way.
  2. Rotate the client secret in the app’s Certificates & secrets blade, or delete it.
  3. If you set up the optional FOCUS lane, remove the storage grant. Then delete or pause the export.

Next: Security and data handling. Where the data these roles read is hosted, and what leaves the EU.