Azure-Samples / Azure-Samples/azure-finops-agent

[Bug]: setup-entra-app.ps1 uses appRole GUIDs as delegated scopes — admin consent fails for all fresh deployments

Open
#52 0 comments 1 reaction 2 assignees Claimed by @alfarahn View on GitHub
Dominant language
C#
Stars
27
Forks
20
Avg merge
3d 7h
Merged PRs (30d)
3

Description

### Summary

`setup-entra-app.ps1` declares three Microsoft Graph / Log Analytics permission GUIDs that are not valid delegated (`Scope`) permissions. Because Entra validates a consent request atomically, **admin consent fails for the entire app**, and no service principal is created. Any fresh `azd up` into a tenant that requires admin consent is blocked at the "Connect Azure" step.

### Affected code

[`src/Dashboard/setup-entra-app.ps1`](https://github.com/Azure-Samples/azure-finops-agent/blob/main/src/Dashboard/setup-entra-app.ps1) (lines 138, 141, 145 — used as `type = "Scope"` on lines 163, 166, 172):

```powershell
$graphOrgReadAll = "498476ce-e0fe-48b0-b801-37ba7e2685c6" # Organization.Read.All
$graphGroupReadAll = "5b567255-7703-4780-807c-7be8301ae99b" # Group.Read.All
$laDataRead = "e4aa47b9-9a69-4109-82ed-36ec70d85f3f" # Data.Read
```

### Root cause

Two of the GUIDs are **application role** IDs (app-only permissions), not delegated scopes, yet they are declared as `type = "Scope"`. The third does not exist on the Log Analytics API at all.

| Variable | Current value | What it actually is |
|---|---|---|
| `$graphOrgReadAll` | `498476ce-...` | `Organization.Read.All` **appRole** (app-only) |
| `$graphGroupReadAll` | `5b567255-...` | `Group.Read.All` **appRole** (app-only) |
| `$laDataRead` | `e4aa47b9-...` | not present in Log Analytics `oauth2PermissionScopes` |

The script's own output text (lines 190–194) states these are intended to be delegated:

> `Log Analytics: Data.Read (delegated, read-only)`
> `Microsoft Graph: ... (all delegated, read-only)`

So the intent is delegated scopes; the values were taken from the appRole column.

### Reproduction

1. `azd up` into a tenant where "Users can register applications" / user consent is restricted.
2. Sign in, click **Connect Azure**.
3. Entra shows **"Need admin approval"**.
4. A Global/Application Administrator grants consent — it silently fails.
5. `az ad sp show --id ` → `Resource does not exist` (no service principal was ever created).

### Verification

```powershell
# Log Analytics API publishes exactly one delegated scope, and it is not e4aa47b9-...
az ad sp show --id ca7f3f0b-7d91-482c-8e09-c5d840d0eac5 `
--query "oauth2PermissionScopes[].{id:id,value:value}" -o json
# => [ { "id": "e8dac03d-d467-4a7e-9293-9cca7df08b31", "value": "Data.Read" } ]

# The two Graph GUIDs resolve as appRoles, not oauth2PermissionScopes
az ad sp show --id 00000003-0000-0000-c000-000000000000 `
--query "appRoles[?id=='498476ce-e0fe-48b0-b801-37ba7e2685c6'].value" -o tsv
# => Organization.Read.All
```

These are Microsoft first-party service principals, so the IDs are identical in every tenant — this is not environment-specific.

### Proposed fix

```diff
-$graphOrgReadAll = "498476ce-e0fe-48b0-b801-37ba7e2685c6" # Organization.Read.All
+$graphOrgReadAll = "4908d5b9-3fb2-4b1e-9336-1888b7937185" # Organization.Read.All

-$graphGroupReadAll = "5b567255-7703-4780-807c-7be8301ae99b" # Group.Read.All
+$graphGroupReadAll = "5f8c59db-677d-491f-a6b8-5f174b11ec1d" # Group.Read.All

-$laDataRead = "e4aa47b9-9a69-4109-82ed-36ec70d85f3f" # Data.Read
+$laDataRead = "e8dac03d-d467-4a7e-9293-9cca7df08b31" # Data.Read
```

After applying this, every requested ID resolves against its resource's published `oauth2PermissionScopes`, and admin consent succeeds.

### Secondary issue: silent failure of the redirect-URI patch

[`infra/scripts/postprovision.ps1`](https://github.com/Azure-Samples/azure-finops-agent/blob/main/infra/scripts/postprovision.ps1) (line 40) patches the production redirect URI:

```powershell
az ad app update --id $objectId --web-redirect-uris @merged --output none
```

In my run this left only `http://localhost:5000/auth/microsoft/callback` on the app registration — the `https:///auth/microsoft/callback` entry was never added, which would have produced `AADSTS50011` after consent. The script's fallback branch (line 45) prints a warning, but it scrolls past in normal `azd up` output.

Suggestion: fail loudly (non-zero exit) when the redirect-URI patch does not apply, or re-read and assert the URIs afterwards.

### Suggested hardening

Rather than hardcoding GUIDs, resolve them at runtime so this class of bug cannot recur:

```powershell
$laDataRead = az ad sp show --id $laAppId `
--query "oauth2PermissionScopes[?value=='Data.Read'].id" -o tsv
```

### Environment

- `azd` 1.25.6
- Azure CLI 2.83.0
- PowerShell 7.5.8 / Windows 11
- Repo `main` as of commit `758c2c9`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.