Dashboard entry RBAC gate enabled by default¶
Motivation¶
Any authenticated member of the configured Entra tenant could load the full
dashboard — cluster names, job titles, database names, the subscription id —
even with zero Azure RBAC on the deployment's resource group / subscription
(Broken Access Control, OWASP A01). The ENFORCE_DASHBOARD_RBAC entry gate
already existed but shipped default-OFF, so the insecure behaviour was the
default. A blocked user also had no clear "how do I request access" path.
User-facing change¶
- Gate enabled by default.
ENFORCE_DASHBOARD_RBACnow defaults totrueininfra/modules/containerAppControl.bicep. A signed-in tenant member must hold at least a read role (Reader / Contributor / Owner / AKS read / Storage Blob Data reader) on the platform scope beforeGET /api/mesucceeds. Denied callers get the existing access-denied screen instead of a half-broken dashboard. Set the env var tofalseto restore the legacy "any tenant member loads the dashboard" behaviour. - Actionable access-denied screen. The 403
dashboard_access_denieddetail now names the concrete subscription id, resource group, and role (Reader) to request, and theweb/src/pages/AccessDenied.tsxscreen tells the user to forward that to a subscription owner / administrator, plus a note that the role can take a minute to propagate before Retry.
API / IaC diff summary¶
api/services/dashboard_access.py: the 403 detail gains asubscription_idfield and themessagenow interpolates the subscription id + resource group- role so a blocked caller can forward an exact access request.
resource_groupis unchanged (additive change — existing consumers keep working). infra/modules/containerAppControl.bicep:ENFORCE_DASHBOARD_RBACflippedfalse->truewith an updated comment documenting the managed-identityroleAssignments/readprerequisite and the fail-open degrade behaviour.web/src/pages/AccessDenied.tsx: renders the now-specific backend message and replaces the redundant static hint with a propagation/retry note.
Prerequisite (operator action)¶
The gate is only effective when the shared api managed identity can read role
assignments. Grant the dashboard managed identity Reader at subscription
scope (built-in Reader includes Microsoft.Authorization/roleAssignments/read).
Without it, enumeration fails and the gate degrades OPEN (logs a warning,
lets everyone in) so it can never cause a tenant-wide lockout. Enabling the gate
requires a redeploy / Container App env update to take effect.
Persona impact (charter 12a)¶
This is a security tightening of the read entry surface, gated behind the
existing ENFORCE_DASHBOARD_RBAC flag (no role narrowed — RBAC 2-phase rule
N/A). The gate degrades OPEN, so no persona is ever locked out by a transient
ARM hiccup or a missing MI Reader role.
| Persona | Before | After |
|---|---|---|
owner_caller |
loads dashboard | loads dashboard (has read role) |
contributor_caller |
loads dashboard | loads dashboard (has read role) |
reader_caller |
loads dashboard | loads dashboard (has Reader) |
| no-role tenant member | loads full dashboard | 403 + access-denied screen with request guidance |
dev_bypass_caller |
local only | unchanged (dev bypass always allowed) |
No Depends(require_caller) added to an SSE stream. The Reader allowlist is
unchanged.
Validation evidence¶
uv run pytest -q api/tests/test_dashboard_access.py api/tests/test_persona_matrix.py api/tests/test_me_route.py-> 76 passed.ENFORCE_DASHBOARD_RBAC=true uv run pytest -q api/tests/test_persona_matrix.py api/tests/test_dashboard_access.py(gate forced ON, charter 12a Rule 4 evidence) -> 65 passed.uv run ruff check api/services/dashboard_access.py api/tests/test_dashboard_access.py-> clean.cd web && npm run build-> built successfully (AccessDenied typechecks).