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
- 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
Assessment
This issue has not been assessed yet.