You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
infra: assign least-privilege Cognitive Services User when ASSIGN_RBAC=true
Replaces the two-role assignment (Foundry User + Foundry Project Manager) with a single Cognitive Services User assignment on the Foundry account. This is the smallest built-in role whose data actions cover Microsoft.CognitiveServices/accounts/MaaS/* (the Claude inference path).
Preserves the issue #38 / #39 serialization: all three Claude deployments still dependsOn the role-assignment resource, so the model-deployment LRO absorbs RBAC propagation lag.
Verified end-to-end with ASSIGN_RBAC=true: azd up succeeded, verifier 10/10 PASS including live claude -p round trip, python src/hello_claude.py succeeded on first call with no retries.
Closes#40.
Copy file name to clipboardExpand all lines: .github/copilot-instructions.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,7 +28,7 @@ This is the **Claude on Foundry Starter Kit** ([`Azure-Samples/claude`](https://
28
28
|`CLAUDE_OPUS_MODEL`| no | empty | e.g. `claude-opus-4-8`. Empty = skip family. |
29
29
|`CLAUDE_HAIKU_CAPACITY` / `CLAUDE_SONNET_CAPACITY` / `CLAUDE_OPUS_CAPACITY`| no |`25`| TPM ÷ 1000 per family |
30
30
|`CLAUDE_MODEL_NAME`| no |`claude-sonnet-4-6`|**Legacy** single-deployment fallback (only used when all three `CLAUDE_*_MODEL` are empty) |
31
-
|`ASSIGN_RBAC`| no |`false`|`true` grants Foundry User + Foundry Project Manager to `AZURE_PRINCIPAL_ID`|
31
+
|`ASSIGN_RBAC`| no |`false`|`true` grants `Cognitive Services User` (least-privilege inference role) on the Foundry account to `AZURE_PRINCIPAL_ID`|
32
32
|`CLAUDE_CODE_AUTO_INSTALL`| no |`false`|`true` installs the Claude Code CLI in the postprovision hook |
33
33
|`CLAUDE_WRITE_VSCODE_SETTINGS`| no |`false`|`true` opts in to having the postprovision hook write `.vscode/settings.json` for the [Anthropic Claude Code VS Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code). Default skips it — the CLI / SDK don't need workspace settings. |
Copy file name to clipboardExpand all lines: README.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -240,7 +240,7 @@ Rows marked **Attest** below are the three `modelProviderData` fields sent to An
240
240
| `CLAUDE_MODEL_VERSION` | no | `1` | Applies to all deployed families. |
241
241
| `CLAUDE_MODEL_NAME` | no | `claude-sonnet-4-6` | **Legacy.** Only used when all three `CLAUDE_*_MODEL` vars are empty (single-deployment fallback). |
242
242
| `CLAUDE_MODEL_CAPACITY` | no | `25` | **Legacy.** Capacity for the legacy single-deployment fallback. |
243
-
| `ASSIGN_RBAC` | no | `false` | `true` to grant `Foundry User` + `Foundry Project Manager` to `AZURE_PRINCIPAL_ID` (needs `roleAssignments/write`) |
243
+
| `ASSIGN_RBAC` | no | `false` | `true` to grant `Cognitive Services User` (least-privilege inference role) on the Foundry account to `AZURE_PRINCIPAL_ID` (needs `roleAssignments/write`) |
244
244
| `CLAUDE_CODE_AUTO_INSTALL` | no | `false` | `true` to let the postprovision hook run the official Claude Code installer ([`install.ps1`](https://claude.ai/install.ps1) / [`install.sh`](https://claude.ai/install.sh)) when `claude` isn't already on PATH |
245
245
246
246
### Choosing which models to deploy
@@ -426,9 +426,9 @@ If the Anthropic SDK ever accepts a callable for `auth_token`, this shim becomes
- One **Claude deployment per requested family** (`GlobalStandard`, with the required `modelProviderData` block) — set `CLAUDE_HAIKU_MODEL` / `CLAUDE_SONNET_MODEL` / `CLAUDE_OPUS_MODEL` to control which families. Sonnet/Opus deployments chain on the prior to avoid Foundry's per-account 409s on concurrent create.
429
-
- *Optional* RBAC: *Foundry User* + *Foundry Project Manager* on the deploying principal (set `ASSIGN_RBAC=true`). *(These roles were previously called `Azure AI User`/`Azure AI Project Manager`; Azure renamed them — the underlying role GUIDs are unchanged.)*
429
+
- *Optional* RBAC: a single `Cognitive Services User` assignment on the Foundry account for the deploying principal (set `ASSIGN_RBAC=true`). This is the [least-privilege role the MS Learn doc recommends](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/configure-entra-id?tabs=python&pivots=ai-foundry-portal#for-making-authenticated-api-calls) for keyless inference — it grants exactly the `Microsoft.CognitiveServices/accounts/MaaS/*` data action this template's runtime needs and nothing else. If you want broader access (project-scoped APIs, agents, etc.), grant `Foundry User` or `Azure AI Developer` yourself afterwards — see the [permissions matrix](#required-permissions) below.
430
430
- **Heads up:** without this (or a manual post-deploy grant), the Python SDK and `claude` CLI will return `401 PermissionDenied` even though `azd up` succeeded. See [Granting data-plane roles after `azd up`](#granting-data-plane-roles-after-azd-up).
431
-
- When `ASSIGN_RBAC=true`, the model deployments are ordered to run *after*both role assignments. The role-assignment PUTs return fast (~5 s) but Foundry data-plane RBAC takes a few minutes to propagate; the slow model-deployment LRO (30 s–20 min) absorbs that propagation time so the first call after `azd up` succeeds without retries.
431
+
- When `ASSIGN_RBAC=true`, the model deployments are ordered to run *after* the role assignment. The role-assignment PUT returns fast (~5 s) but Foundry data-plane RBAC takes a few minutes to propagate; the slow model-deployment LRO (30 s–20 min) absorbs that propagation time so the first call after `azd up` succeeds without retries.
432
432
433
433
</details>
434
434
@@ -478,7 +478,7 @@ claude/
478
478
| Preflight: `Marketplace offer ... not found` | `CLAUDE_MODEL_NAME` is misspelled, the model isn't in the Anthropic-on-Foundry catalog yet, or Anthropic changed the plan-name convention. |
479
479
| Preflight: `Quota insufficient` (exit 6) | Requested `CLAUDE_*_CAPACITY` plus existing usage exceeds the per-region quota limit. Lower the requested capacity, free up quota by deleting unused deployments, or [purge soft-deleted accounts](#free-quota-held-by-soft-deleted-accounts) that may still be holding TPM. |
480
480
| Quota looks full but you have no live deployments (`az cognitiveservices usage list` shows `currentValue > 0`, deployment still fails with `715-123420` / `InsufficientQuota`) |**Soft-deleted Cognitive Services accounts still reserve quota for48 h.** A previous `azd down` (or any RG / account delete) puts the AIServices accountin a recoverable state that keeps holding TPM. **Fix:** list and purge them: `az cognitiveservices account list-deleted -o table`then`az cognitiveservices account purge --name <name> --location <region> --resource-group <rg>`for each. See [Free quota held by soft-deleted accounts](#free-quota-held-by-soft-deleted-accounts). |
481
-
| `401 PermissionDenied: Principal does not have access to API/Operation` intermittently — same code passes seconds later | Data-plane RBAC propagation lag on a freshly-granted role (`Cognitive Services User` / `Foundry User` / `Azure AI Developer`). The grant can take a few minutes to land on the Foundry data plane even after `az role assignment create` returns. When `ASSIGN_RBAC=true`, this kit serializes the model deployments after the role assignments so the deployment LRO absorbs the propagation wait — the first call after `azd up` should just work. If you granted the role manually *after* `azd up`, wait a minute and retry; verify the assignment with `az role assignment list --assignee <oid> --scope <foundry-account-id> -o table`. |
481
+
|`401 PermissionDenied: Principal does not have access to API/Operation` intermittently — same code passes seconds later | Data-plane RBAC propagation lag on a freshly-granted role (`Cognitive Services User` / `Foundry User` / `Azure AI Developer`). The grant can take a few minutes to land on the Foundry data plane even after `az role assignment create` returns. When `ASSIGN_RBAC=true`, this kit serializes the model deployments after the role assignment so the deployment LRO absorbs the propagation wait— the first call after `azd up` should just work. If you granted the role manually *after*`azd up`, wait a minute and retry; verify the assignment with `az role assignment list --assignee <oid> --scope <foundry-account-id> -o table`. |
482
482
|`claude -p` returns `The model claude-<family>-... is not available on your foundry deployment. Try --model to switch to ...`| Your user-global `~/.claude/settings.json` has `"model"`set to a family this workspace didn't deploy. The postprovision hook writes a workspace `.claude/settings.json` with `"model"` pinned to a deployed family, which overrides the global — but if you re-ran `azd up` *before* the hook update, or your global has a per-project override, the workspace pin won't apply. Either re-run `pwsh -File scripts/configure-claude-code.ps1` to regenerate `.claude/settings.json`, pick the family explicitly via `claude -p --model <sonnet\|opus\|haiku>`, or edit `~/.claude/settings.json` to remove the `"model"` line. |
483
483
| Windows: `UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f60a'` printing the model's response | The Foundry sample apps happily return emoji and other non-CP1252 characters; the default Windows console (cp1252) can't render them. Either set `$env:PYTHONIOENCODING = "utf-8"` before running, or switch the console to UTF-8 with `chcp 65001`. The Python samples already handle this gracefully, but third-party tooling may not. |
484
484
| `check_claude_quota.py` exits with `Could not resolve a subscription id ... [WinError 2] The system cannot find the file specified` | The script falls back to `az account show` to find a subscription, but the Azure CLI isn't on `PATH`in the active shell. Either set`$env:AZURE_SUBSCRIPTION_ID = "<sub-id>"` or pass `--subscription <sub-id>` explicitly. |
@@ -640,7 +640,7 @@ Notes on the output:
640
640
|---|---|---|
641
641
| Provision Foundry + Claude deployment |`Contributor` (or `Cognitive Services Contributor`) | Resource group / subscription |
642
642
| Assign RBAC inside this template (`ASSIGN_RBAC=true`) |`User Access Administrator` or `Owner`| Resource group / subscription |
643
-
| Call the Messages API with Entra ID | `Foundry User` *(or `Azure AI Developer` — see note)* | Foundry account |
643
+
| Call the Messages API with Entra ID |`Cognitive Services User`*(template default; see notefor broader alternatives)*| Foundry account |
644
644
645
645
If you do not have `Microsoft.Authorization/roleAssignments/write`, leave `ASSIGN_RBAC=false` (the default) and ask an admin to grant one of the roles below on the Foundry account afterwards.
646
646
@@ -672,8 +672,8 @@ Wait 1–3 minutes for the role to propagate to the Foundry data plane before re
672
672
673
673
| Role | Data action(s) | Notes |
674
674
|---|---|---|
675
-
| `Cognitive Services User` | `Microsoft.CognitiveServices/*/read` + inference action | The minimum role recommended by [the official docs](https://learn.microsoft.com/azure/ai-foundry/foundry-models/how-to/use-foundry-models-claude#troubleshooting). |
676
-
| `Foundry User` | `Microsoft.CognitiveServices/*` | Broadest data-plane access; what this template assigns when `ASSIGN_RBAC=true`. **Previously named `Azure AI User`** — Azure renamed it, GUID `53ca6127-db72-4b80-b1b0-d745d6d5456d` is unchanged. |
675
+
|`Cognitive Services User`|`Microsoft.CognitiveServices/*/read` + inference action | The minimum role recommended by [the official docs](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/configure-entra-id?tabs=python&pivots=ai-foundry-portal#for-making-authenticated-api-calls), and what this template assigns when `ASSIGN_RBAC=true`. GUID `a97b65f3-24c7-4388-baec-2e87135dc908`. |
676
+
|`Foundry User`|`Microsoft.CognitiveServices/*`|Broader data-plane access;useful if you plan to add project-scoped samples (agents, knowledge, evaluators) on top of this template. **Previously named `Azure AI User`**— Azure renamed it, GUID `53ca6127-db72-4b80-b1b0-d745d6d5456d` is unchanged. |
677
677
|`Azure AI Developer`| includes `Microsoft.CognitiveServices/accounts/MaaS/*`| Sufficient for Claude because Claude routes through the **MaaS** data path as a partner/marketplace model. (It is **not** sufficient for first-party Foundry models that route through `accounts/AIServices/*`.) |
678
678
679
679
> The role `Azure AI Developer` was historically called out as *insufficient*for Foundry inference. That guidance still applies to first-party `AIServices` models, but Claude/Anthropic deployments dispatch through `Microsoft.CognitiveServices/accounts/MaaS/*`, which `Azure AI Developer` already grants. Verified against `claude-sonnet-4-6` on `2025-10-01-preview`.
Copy file name to clipboardExpand all lines: infra-bicep/infra/main.bicep
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,7 @@ param location string
34
34
@description('Object id of the deploying user/SP. Empty disables RBAC.')
35
35
paramprincipalIdstring = ''
36
36
37
-
@description('Whether to assign Foundry User + Foundry Project Manager (formerly Azure AI User / Project Manager) to principalId. Set to "true" to enable.')
37
+
@description('Whether to assign Cognitive Services User (least-privilege inference role) to principalId on the Foundry account. Set to "true" to enable.')
0 commit comments