Access review — per-resource-group "View my access"¶
Motivation¶
When the dashboard is stood up in a fresh tenant, the most common failure mode
is a missing Azure RBAC role on one of the resource groups the control plane
touches (workload/dashboard RG, ACR RG, terminal RG, AKS cluster RG). These
failures surface 30-60 s later as opaque AuthorizationFailed errors, and the
only way to diagnose them today is to open the Azure portal, navigate to each
resource group's Access control (IAM) → View my access, and eyeball the
inherited role assignments.
This change brings that portal experience into the dashboard's Settings panel so an operator can spot the gap without leaving the app.
User-facing change¶
New Settings → Diagnose & solve problems section, structured like the Azure portal's diagnostics landing: it lists diagnostic categories as cards — Identity and Security (available now), plus Connectivity Issues, Reliability, and Availability and Performance as "Coming soon" placeholders for future work. Clicking Identity and Security opens a focused detail view (with a back link) that has a My access / Dashboard identity toggle:
- My access shows the signed-in user account (display name, account/UPN,
tenant id, object id), resolved from the MSAL active account and confirmed
against
/api/me. - Dashboard identity shows the shared managed identity the Container App
runs as (object id from
SHARED_IDENTITY_PRINCIPAL_ID) — the principal that actually performs ARM and Storage writes, so its missing role is the usual root cause of a tenant onboarding failure even when the user's own access looks fine. In a local-dev shell with no managed identity, the view reports the identity as unavailable instead of a misleading empty table.
For the selected principal it lists, per resource group, the effective Azure
RBAC role assignments — direct plus those inherited from the subscription,
management groups, and (for the user) their Entra groups — each tagged Direct
/ Inherited with its scope level.
Resource groups where role enumeration fails (the caller lacks
Microsoft.Authorization/roleAssignments/read) are flagged as a finding rather
than silently shown as "has access". A no-role resource group is called out
explicitly. The resource groups reviewed are derived from the saved workspace
config (workload, ACR, terminal) plus sub-wide AKS cluster discovery.
The Settings footer now also shows the running build version
(v<A>.<B>.<build> · <short-sha>) to the left of the existing "Stored locally ·
elb-prefs" label, so the active control-plane version is visible from any
Settings tab.
API / IaC diff summary¶
- New route
GET /api/me/access-review?subscription_id=…&resource_group=…&target=me|dashboard(repeatresource_groupto review several at once;targetselects the signed-in caller or the dashboard managed identity),require_caller-gated, read-only. Returns{ subscription_id, principal: { kind, object_id, available }, groups: [{ resource_group, scope, assignments: [{ role_name, role_guid, scope_level, inherited, assignment_scope }], degraded, reason }] }. - New service
api/services/access_review.py— one ARM enumeration viaassignedTo('{oid}')(group-inheritance aware, same semantics as the portal), grouped per RG with inheritance/scope-level classification and lazy custom-role name resolution.review_resource_group_accesstakes aprincipal_oid+principal_kindso it can review either the caller or the dashboard MI;dashboard_identity_principal_id()readsSHARED_IDENTITY_PRINCIPAL_ID. Unlikecompute_caller_permissions, it does not degrade open: enumeration failure is reported asdegraded=true+ reason. - Frontend
web/src/api/me.tsgainsmeApi.accessReview()+ theAccessReviewResponse/AccessReviewGroup/AccessReviewRowtypes, andSettingsPanel.tsxgains theDiagnosticsSection+RgAccessCardcomponents and the "Diagnose & solve problems" nav entry.
No IaC change. No RBAC/network/auth contract change — this is a read-only diagnostic behind the existing caller gate, so §12a hardening discipline does not apply.
Validation evidence¶
uv run pytest -q api/tests→ 2790 passed, 3 skipped. New:api/tests/test_access_review.py(11 tests) +test_me_access_review_*inapi/tests/test_me_route.py.uv run ruff check api/services/access_review.py api/routes/me.py …→ clean.cd web && npm run build→ built in ~9 s, no type errors.cd web && npm test -- --run→ 624 passed.npx eslint src/components/SettingsPanel.tsx src/api/me.ts→ 0 errors (1 pre-existing unrelated warning at line 2144).