Skip to content

Getting started

Entra app registration

Create the read-only service principal Infralign uses to pull your Azure cost and resource data, and grant it the two read-only RBAC roles the daily lane needs.

Validated against the Azure portal on 27 July 2026.

Click any image to enlarge.

Infralign reads your Azure estate through a service principal — an app registration in your own Microsoft Entra tenant that you create, control, and can revoke at any time.

The one-time admin consent your tenant grants is for the separate Infralign Portal sign-in app — see Dashboard sign-in. The roles granted here are the ones set out in the permissions reference.

Connecting your Azure estate to Infralign, in under an hour. An amber band labelled your team, your Azure tenant — marked read-only and no Graph permissions, no write access — holds two cards: step 1, register the app, and step 2, add a client secret, tagged Entra admin; then step 3, assign 2 reader roles, and step 4, wait about ten minutes for RBAC to propagate, tagged subscription owner, per subscription. An arrow labelled step 5, three ids by email and the secret by a one-time link, runs down to a cyan Infralign band: validated, then read nightly 02:30 to 06:15. Connecting your Azure estate to Infralign, in under an hour. An amber band labelled your team, your Azure tenant — marked read-only and no Graph permissions, no write access — holds two cards: step 1, register the app, and step 2, add a client secret, tagged Entra admin; then step 3, assign 2 reader roles, and step 4, wait about ten minutes for RBAC to propagate, tagged subscription owner, per subscription. An arrow labelled step 5, three ids by email and the secret by a one-time link, runs down to a cyan Infralign band: validated, then read nightly 02:30 to 06:15.
Figure 1 — Steps 1 to 4 happen entirely inside your tenant; only four values ever leave it, at step 5. Revoke at any time — delete the role assignments or the app registration and ingestion stops the same moment.

Figure 1 shows who does each step and which four values leave your tenant. The steps below are the portal path.

  1. Sign in to the Microsoft Entra admin center as a user who can register applications.
  2. Go to Identity → Applications → App registrations → New registration.
  3. Name it something recognisable, for example infralign-reader.
  4. Under Supported account types, choose single tenant (accounts in this organisational directory only).
  5. Leave Redirect URI empty — the app authenticates as a service principal, not through a browser sign-in, so it needs no redirect.
  6. Select Register.
  7. On the app’s Overview blade, record the Application (client) ID and the Directory (tenant) ID. Infralign needs both.

App registrations blade in the Microsoft Entra admin center with the New registration action highlighted at the top of the list

Register an application form with the name infralign-reader entered, single tenant selected, and the redirect URI left empty

App overview blade showing the Application (client) ID and Directory (tenant) ID fields to record

The infralign-reader app in these captures was deleted afterwards; the IDs and secret shown are no longer valid.

  1. On the app, open Certificates & secrets → Client secrets → New client secret.
  2. Add a description (for example infralign) and an expiry. We recommend 6–12 months, and rotate on that schedule. For security-sensitive tenants, ask Infralign about certificate credentials or workload-identity federation instead.
  3. Select Add, then copy the secret Value immediately — Azure shows it only once and it cannot be retrieved later.
  4. Share the value only through the one-time password-manager link Infralign sends you — see what Infralign needs from you. Never send it by email or chat in plain text.

Add a client secret panel with a description entered and an expiry selected

Client secrets tab listing the new secret with its expiry date; the secret value is shown once and must be copied immediately

Once shared, the secret is held in access-controlled configuration on Infralign’s infrastructure — encrypted at rest, scoped to the ingestion runtime, and never logged. See security and data handling for how credentials are held.

This step is Azure role-based access control — role assignments on your resources — not API permissions. The default Cost API lane needs exactly two built-in roles, both assigned per subscription on every subscription you want analysed:

  • Cost Management Reader — the daily scrape calls POST …/providers/Microsoft.CostManagement/generateCostDetailsReport, which is an action, not a read, and plain Reader returns 403 on it. Report blobs download over Cost-Management-signed SAS links, so no storage role is involved. The billing-account scope is never used.
  • Reader — its */read covers everything else: Azure Resource Graph inventory, the Activity Log, Azure Monitor metrics, and Azure Advisor recommendations.

Reader grants control-plane metadata only. It can see that resources exist and how they are configured, never what is inside them. If your security team wants the detail, or a stricter alternative, see Why Reader, and what it cannot see.

For each subscription, the portal path is the same:

  1. Open the subscription’s Access control (IAM) blade.
  2. Select Add → Add role assignment.
  3. Choose the role, then Next.
  4. On Members, keep User, group, or service principal, select Select members, and search for the app by name (infralign-reader).
  5. Select Review + assign.

Repeat both assignments on every subscription you include — Infralign fans out across all granted subscriptions.

Data sourceEndpoint · api-versionRoleWhat breaks without it
Cost Details reportPOST …/subscriptions/{sub}/providers/Microsoft.CostManagement/generateCostDetailsReport · 2024-08-01 (ActualCost + AmortizedCost)Cost Management Reader403 on the report action — no daily spend, no dashboards, reports, or audit baselines
Resource Graph inventoryPOST …/providers/Microsoft.ResourceGraph/resources · 2022-10-01 (also AdvisorResources + ResourceChanges tables)ReaderNo inventory, no cost-to-resource mapping, no orphaned-disk detection
Activity LogGET …/subscriptions/{sub}/providers/Microsoft.Insights/eventtypes/management/values · 2015-04-01ReaderNo control-plane change history behind cost movements
Azure Monitor metricsGET {resourceId}/providers/microsoft.insights/metrics · 2023-10-01ReaderNo CPU utilisation — idle-VM and rightsizing confidence collapse
Azure AdvisorMicrosoft.Advisor/recommendations/read (via Resource Graph)ReaderNo Advisor cost recommendations as an input signal

The CLI equivalent of the two sections above — use this or the portal path, not both.

Sign in as a user who can register applications and assign roles (not the service principal), then replace the placeholders with your own IDs.

Terminal window
# Create the app registration and its service principal, capturing the appId
appId=$(az ad app create --display-name infralign-reader --query appId -o tsv)
az ad sp create --id "$appId"
# Role assignments target the service principal's object id, not the appId
spObjectId=$(az ad sp show --id "$appId" --query id -o tsv)
# Grant the two read-only roles, per subscription
az role assignment create \
--assignee-object-id "$spObjectId" \
--assignee-principal-type ServicePrincipal \
--role "Cost Management Reader" \
--scope /subscriptions/<subscription-id>
az role assignment create \
--assignee-object-id "$spObjectId" \
--assignee-principal-type ServicePrincipal \
--role "Reader" \
--scope /subscriptions/<subscription-id>
# Confirm the assignments landed
az role assignment list --assignee "$appId" --all --output table

Repeat both assignments for every subscription you want analysed.

Once the two roles are assigned, confirm the service principal can actually read each source before handing the credentials over.

  1. On each subscription, open Access control (IAM) → Role assignments and confirm infralign-reader appears twice — once as Cost Management Reader and once as Reader.
  2. On the app registration, open Certificates & secrets and confirm a client secret is listed with an expiry in the future. An expired secret authenticates nothing.

Paste these in order, replacing <appId>, <tenant-id>, <subscription-id>, <rg>, and <vm-name> with your own values. Probes 1–4 are the pass/fail gate — if all four pass, the credential authenticates and both roles are live, which is everything the handover needs. The per-source probes after them are diagnostics, folded away below. A probe that fails with AuthorizationFailed means the role behind it is missing. RBAC changes take up to ten minutes to propagate, so a fresh assignment may return 403 until it takes effect — wait, then re-run.

  1. Sign in as the service principal. Proves the client id + secret pair is valid. Read the secret into a variable rather than typing it — a plain -p <secret> lands in shell history, which both bash and PSReadLine persist.

    Terminal window
    read -rsp "client secret: " SP_SECRET; echo
    az login --service-principal -u <appId> -p "$SP_SECRET" --tenant <tenant-id>

    The output lists one object per subscription the service principal can see:

    [ { "cloudName": "AzureCloud", "id": "<subscription-id>", "state": "Enabled",
    "user": { "name": "<appId>", "type": "servicePrincipal" } } ]
  2. Set the subscription context. Run the probes against one subscription at a time.

    Terminal window
    az account set --subscription <subscription-id>
  3. Check the management token. A future expiresOn confirms the credential can call Azure Resource Manager.

    Terminal window
    az account get-access-token --resource https://management.azure.com --query expiresOn
  4. List the role assignments. Both roles should appear, scoped to the subscription.

    Terminal window
    az role assignment list --assignee <appId> --all -o table
    Principal Role Scope
    ----------------- ---------------------- -------------------------------------
    infralign-reader Cost Management Reader /subscriptions/<subscription-id>
    infralign-reader Reader /subscriptions/<subscription-id>
Per-source probes — run these if a dashboard tile comes up empty
  1. Probe cost (Cost Management Reader). This validates the cost data-plane. The nightly lane calls generateCostDetailsReport (api-version 2024-08-01), covered by the same role.

    Terminal window
    az rest --method post \
    --url "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/query?api-version=2023-11-01" \
    --body '{"type":"ActualCost","timeframe":"MonthToDate","dataset":{"granularity":"Daily","aggregation":{"totalCost":{"name":"Cost","function":"Sum"}}}}'
    { "properties": { "columns": [ {"name":"Cost","type":"Number"},
    {"name":"UsageDate","type":"Number"}, {"name":"Currency","type":"String"} ],
    "rows": [ [3.14729643469101, 20260710, "EUR"],
    [6.3827424306758, 20260711, "EUR"], ] } }

    A failure here names the action Microsoft.CostManagement/query/action.

    Optionally, exercise the exact ingestion endpoint the nightly lane uses:

    Terminal window
    az rest --method post \
    --url "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2024-08-01" \
    --body '{"metric":"ActualCost","timePeriod":{"start":"<yyyy-mm-01>","end":"<yyyy-mm-07>"}}'

    Expected result: HTTP 202 with a Location header to poll.

  2. Probe Resource Graph (Reader). The extension-free az rest call works anywhere; az graph query is the alternative if you have the resource-graph extension.

    Terminal window
    # Extension-free: call the REST endpoint directly
    az rest --method post \
    --url "https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01" \
    --body '{"subscriptions":["<subscription-id>"],"query":"Resources | summarize count()"}'
    Terminal window
    # Alternative: the extension route (idempotent add)
    az extension add --name resource-graph
    az graph query --subscriptions <subscription-id> -q "Resources | summarize count()"
    { "count": 1, "data": [ { "count_": 17 } ], "skip_token": null, "total_records": 1 }

    A failure here names Microsoft.ResourceGraph/resources/read.

  3. Probe the Activity Log (Reader).

    Terminal window
    az monitor activity-log list --subscription <subscription-id> --offset 24h --max-events 1 -o json
    [ { "caller": "Microsoft.Advisor", "category": { "value": "Recommendation" },
    "eventTimestamp": "2026-07-24T03:05:11.1570623Z", "level": "Informational", } ]

    An empty list on a quiet subscription is valid — widen --offset to 7d or more before reading it as a failure.

  4. Probe Advisor (Reader). Advisor recommendations are read through Resource Graph — the same path the platform uses.

    Terminal window
    az graph query --subscriptions <subscription-id> \
    -q "advisorresources | where type == 'microsoft.advisor/recommendations' | limit 1"
    { "count": 1, "data": [ { "properties": { "category": "Cost",
    "impact": "Medium", "impactedField": "Microsoft.Compute/virtualMachines" } } ],
    "total_records": 1 }

    Empty output with HTTP 200 is valid — Advisor may not have generated recommendations yet — and still proves read access.

  5. Probe Monitor metrics (Reader). Point it at any cost-significant VM that is running.

    Terminal window
    az monitor metrics list \
    --resource "/subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachines/<vm-name>" \
    --metric "Percentage CPU" --interval PT1H --offset 3h -o json
    { "interval": "PT1H", "namespace": "Microsoft.Compute/virtualMachines",
    "value": [ { "name": { "value": "Percentage CPU" }, "timeseries": [ { "data": [
    { "average": 36.88, "timeStamp": "2026-07-24T04:43:00Z" },
    { "average": 5.98, "timeStamp": "2026-07-24T05:43:00Z" } ] } ], "unit": "Percent" } ] }

    An empty timeseries on a stopped or idle VM is valid — pick a running VM to see data.

When probes 1–4 pass, repeat from step 2 for the next subscription.

If sign-in is blocked by Conditional Access

Section titled “If sign-in is blocked by Conditional Access”

If the service-principal sign-in fails with an AADSTS error — for example AADSTS53003 — a workload-identity Conditional Access policy is blocking it. Exclude the infralign-reader app from that policy, or scope an exception for it. If the policy is IP-bound, Infralign can supply the egress IPs the ingestion runtime signs in from.

To start ingestion, email [email protected] with:

  • the Directory (tenant) ID;
  • the Application (client) ID;
  • the subscription IDs to analyse.

Infralign replies with a one-time password-manager share link for the fourth value — the client secret. Paste the secret into that link, not into the email body, an attachment, or a chat message.

Rotate the secret at any time — add a new one, share it, and let the old one expire. Access is revocable instantly: delete the role assignments or the app registration and ingestion stops the same moment.

The service principal above is for data ingestion only. Your own users sign in to the dashboard through the multi-tenant Infralign Portal app, with their existing Microsoft work account.


Next: Dashboard sign-in — the one-time admin consent, the user list, and sign-in troubleshooting.