Phase 0 — Entitle user-JIT resolver verification
Audience: contributor · Profile:
both· Read this when: you are verifying how the legacy Entra-group resolver behaves.Legacy mechanism. This runbook covers Entitle granting dashboard permissions through Entra groups. The current mechanism is a REST integration that works for any user and takes effect immediately — see
../integrations/beyondtrust/entitle-dashboard-permissions.md.
Validates the resolver behaviour from the Entitle user-JIT design.
After Phase 0 deploys, this confirms:
- The migration ran cleanly (
users.session_permissionscolumn exists). - The Azure OAuth callback computes the union of
default_permissionsacross every matchedoauth_group_mappingsrow (not just the first). - The union is re-applied on every login — losing a group membership in Entra (Entitle revoking the assignment) actually drops the corresponding scope on the next sign-in.
- The admin-set baseline in
users.permissionsis preserved through sign-in;effective_permissions = union(baseline, session). - Pre-Phase-0 admin-set users keep working when no group claim is present in the token.
Takes ~20 minutes. Run on the community edition
(c:\Scripts\VM_CLI\vm-dashboard-community\).
Prerequisites
- Community edition running on the post-Phase-0 image
(
docker compose upfrom the community repo). - Azure OAuth configured (
AZURE_OAUTH_CLIENT_ID/SECRET/TENANT_ID/REDIRECT_URI). - Three Entra security groups available for testing (any names — they
will be wired to dashboard mappings via the
/rbac#groupsadmin page): T_GROUP_A— will map todefault_permissions = {"aws": ["read"]}T_GROUP_B— will map todefault_permissions = {"vms": ["read"]}T_GROUP_C— will map todefault_permissions = {"is_admin": true}- A test Entra account you can assign/unassign from those groups. (Use Entitle for the real flow once Phase 2 lands; for Phase 0 use direct Entra group management.)
Step 1 — Verify the schema migration ran
docker compose exec db psql -U dashboardadmin -d vmclidashboard -c "\d users" | Select-String "session_permissions"
Expected: one line confirming session_permissions | text |.
If the column is missing the migration didn't run — check
docker compose logs app for ALTER errors and confirm the
"ALTER TABLE users ADD COLUMN session_permissions TEXT" entry is
present in database.py:_migrations.
Step 2 — Wire up test group mappings
Sign in as an existing admin user. Open /rbac#groups. For each of the
three Entra groups created in prerequisites, add an oauth_group_mappings
row:
| Entra group ID | Workstream | default_permissions JSON |
|---|---|---|
<oid of T_GROUP_A> |
Hydra |
{"aws": ["read"]} |
<oid of T_GROUP_B> |
Hydra |
{"vms": ["read"]} |
<oid of T_GROUP_C> |
Hydra |
{"is_admin": true} |
(Use whatever workstream already exists in your dev DB — Hydra is the
default seed.)
Step 3 — Union of group-derived permissions on first login
- In Entra, assign the test account to both
T_GROUP_AandT_GROUP_B(notT_GROUP_C). - Sign in via "Sign in with Microsoft" using the test account.
- Assuming this is a new user, the dashboard auto-creates the row. Inspect the resulting row:
docker compose exec db psql -U dashboardadmin -d vmclidashboard -c "SELECT username, permissions, session_permissions FROM users WHERE username LIKE '%test%' ORDER BY created_at DESC LIMIT 1;"
Expected:
- permissions is NULL (admin baseline untouched on auto-create).
- session_permissions is JSON containing both aws:["read"] and
vms:["read"] — proving the union ran, not "first match wins".
- Hit
/api/auth/mewith the resulting token. The response'spermissionsfield should reflect the union (the endpoint returnseffective_permissions_dict, which equals the session map here).
Step 4 — Revocation on the next login
- In Entra, remove the test account from
T_GROUP_B(but leave it inT_GROUP_A). - Sign out and sign in again with the test account.
- Re-inspect the row:
docker compose exec db psql -U dashboardadmin -d vmclidashboard -c "SELECT username, permissions, session_permissions FROM users WHERE username LIKE '%test%' ORDER BY created_at DESC LIMIT 1;"
Expected:
- session_permissions no longer contains vms:["read"]. Only
aws:["read"] remains.
- permissions is still NULL.
This is the load-bearing property — Entitle removing a group assignment in Phase 2+ flows directly through this code path.
Step 5 — Admin baseline survives a JIT-only revocation
- As admin, in
/rbac#users, hand-grant the test userpermissions = {"images": ["read"]}(this is the admin-set baseline, independent of group claims). - In Entra, remove the test account from
T_GROUP_Aas well so it has no matched groups. (If theoauth_group_mappingstable is non-empty and no group matches, login is rejected with?error=not_authorized— which is the documented behaviour.) - Sign in again. Confirm rejection with
not_authorized. - Re-add the test account to
T_GROUP_Aonly. Sign in. - Re-inspect the row:
Expected:
- permissions still contains {"images": ["read"]} (admin baseline
was never touched by the OAuth path).
- session_permissions contains only {"aws": ["read"]}.
- /api/auth/me returns permissions = the union:
{"images": ["read"], "aws": ["read"]}.
Step 6 — JIT admin elevation via dashboard-admin group
- Add the test account to
T_GROUP_C(mapped to{"is_admin": true}). - Sign in.
/api/auth/meshould now returnis_admin: trueeven thoughusers.is_adminfor this row isfalsein the DB. The admin checkmark in the user list still shows unchecked — admin came via the session map. - Hit an admin-only endpoint (e.g.
GET /api/users). It should succeed. - Remove the test account from
T_GROUP_C. Sign in again. /api/auth/mereturnsis_admin: false. The same admin-only endpoint should now 403.
Step 7 — Legacy admin-set user untouched
- As a pre-Phase-0 admin-set local user (no Entra account, signs in via password), sign in.
- Confirm
session_permissionsis NULL (no OAuth callback ran). - Confirm
effective_permissions_dictreturns the admin-set baseline unchanged.
This proves the Phase 0 resolver change is backward compatible.
Step 8 — Roll forward
Phase 0 ships the resolver. The Entra/Entitle bootstrap that produces
the actual dashboard-* groups is Phases 1–2 of the
Entitle user-JIT design. Until then,
the resolver works against any Entra group mapping seeded via the
/rbac#groups admin page — exactly as it did before, but now correctly
computing the union and re-applying on every login.
Step 9 — Rollback
If something goes wrong:
- The
session_permissionscolumn is additive — no destructive migration to reverse. - To disable the new behaviour entirely without code rollback:
UPDATE users SET session_permissions = NULL;The resolver still computeseffective_permissions = permissions_dictonce the column is NULL, matching pre-Phase-0 behaviour exactly. - If a full code rollback is needed, redeploy the previous image; the column will be left in place harmlessly.