feat(admin): add temporary admin access (break-glass) #42

Merged
ryan.mcgee merged 3 commits from feat/admin-break-glass into main 2026-10-22 14:46:14 +00:00
Owner

Stacked on #41 (feat/admin-organizations): this PR's base is that branch so the diff shows only this change. Merge #41 first, then retarget this PR to main.

Summary

  • Phase 3 of the system administration panel: temporary admin access ("break-glass"). System administrators have no access to tenant data; when one has to look inside an organization to repair it, they can take admin access for a limited time, visibly and on the record.

Changes

  • Taking it (organization page in /admin): a reason is required (shown to the members); it lasts TRAEFIK_CONTROL_BREAK_GLASS_MINUTES (default 60, 1 to 1440). It is an ordinary admin membership with expires_at and reason (reversible migration), so every existing permission check works unchanged.
  • Visible: every member sees a notice on every page of the organization naming who has access, until when and why; the members page and the organization list mark it as temporary.
  • On the record: member.break_glass_started, _ended and _expired in the organization's audit log, and organization.break_glass_* in the server audit log. Expiry is done by the system, so audit events gain a system actor (listed in the API's audit event schema).
  • Ends on time, whether or not anything cleans it up: every read of memberships goes through Membership.active/2, so an expired membership is gone everywhere at once. A page left open re-checks the access on every event and message, and a reaper (every minute, safe with several instances) deletes expired rows and writes the audit event.
  • Bounded: needs Admin.authorize_change/1; refused on a suspended organization, for someone who already is a member, and without a reason; never counts as one of the organization's own admins (its last admin still cannot be removed because a temporary one is present); the system administrator can always end it early, and the organization's admins can remove it from their members page.

Verification

  • mix precommit: exit 0. mix credo --strict: no issues. mix dialyzer: passed.
  • mix test: 1704 passed on SQLite and on PostgreSQL; migration rollback and re-apply clean on both.
  • Production image, with the access set to 1 minute: created an organization, redeemed its owner's invitation, took access with a reason (an empty reason was refused), opened the organization and saw the notice with email, time and reason; left the members page open past the expiry and the next event redirected to the list with "Your temporary admin access has ended"; the organization then answered "not found"; the server log showed created, break_glass_started and break_glass_expired (actor system), and the organization's own log the same member events.

Notes

  • A page already open keeps showing what it showed until its next event or message; it cannot act after the access ends.
  • Expired memberships also stop counting for resource SSO access and invitations.
  • Next: system administrator management (promote, invite, demote, with protections).
> **Stacked on #41** (`feat/admin-organizations`): this PR's base is that branch so the diff shows only this change. Merge #41 first, then retarget this PR to `main`. ## Summary - Phase 3 of the system administration panel: temporary admin access ("break-glass"). System administrators have no access to tenant data; when one has to look inside an organization to repair it, they can take admin access for a limited time, visibly and on the record. ## Changes - **Taking it** (organization page in `/admin`): a reason is required (shown to the members); it lasts `TRAEFIK_CONTROL_BREAK_GLASS_MINUTES` (default 60, 1 to 1440). It is an ordinary admin membership with `expires_at` and `reason` (reversible migration), so every existing permission check works unchanged. - **Visible**: every member sees a notice on every page of the organization naming who has access, until when and why; the members page and the organization list mark it as temporary. - **On the record**: `member.break_glass_started`, `_ended` and `_expired` in the organization's audit log, and `organization.break_glass_*` in the server audit log. Expiry is done by the system, so audit events gain a `system` actor (listed in the API's audit event schema). - **Ends on time, whether or not anything cleans it up**: every read of memberships goes through `Membership.active/2`, so an expired membership is gone everywhere at once. A page left open re-checks the access on every event and message, and a reaper (every minute, safe with several instances) deletes expired rows and writes the audit event. - **Bounded**: needs `Admin.authorize_change/1`; refused on a suspended organization, for someone who already is a member, and without a reason; never counts as one of the organization's own admins (its last admin still cannot be removed because a temporary one is present); the system administrator can always end it early, and the organization's admins can remove it from their members page. ## Verification - `mix precommit`: exit 0. `mix credo --strict`: no issues. `mix dialyzer`: passed. - `mix test`: 1704 passed on SQLite and on PostgreSQL; migration rollback and re-apply clean on both. - Production image, with the access set to 1 minute: created an organization, redeemed its owner's invitation, took access with a reason (an empty reason was refused), opened the organization and saw the notice with email, time and reason; left the members page open past the expiry and the next event redirected to the list with "Your temporary admin access has ended"; the organization then answered "not found"; the server log showed `created`, `break_glass_started` and `break_glass_expired` (actor `system`), and the organization's own log the same member events. ## Notes - A page already open keeps showing what it showed until its next event or message; it cannot act after the access ends. - Expired memberships also stop counting for resource SSO access and invitations. - Next: system administrator management (promote, invite, demote, with protections).
feat(admin): add temporary admin access (break-glass)
All checks were successful
Development images / images (push) Successful in 4m44s
9de9f37db0
- let a system administrator take admin access to an organization for
  a limited time (default 60 minutes, TRAEFIK_CONTROL_BREAK_GLASS_MINUTES)
  by giving a reason; it is an ordinary admin membership with
  expires_at and reason
- show every member a notice on every page of the organization while it
  runs, naming who, until when and why
- record start, early end and expiry in the organization's audit log
  and the server audit log; add a system actor for expiry
- expired memberships count as gone in every membership read, a page
  left open re-checks on each event, and a reaper deletes them and
  writes the audit event
- temporary admins never count towards the organization's last admin;
  access needs authorize_change/1, is refused on suspended
  organizations and for existing members, and can always be ended
- list the system actor in the API's audit event schema
ryan.mcgee changed target branch from feat/admin-organizations to main 2026-10-22 14:41:11 +00:00
chore: merge main (squash of #41) into feat/admin-break-glass
All checks were successful
Development images / images (push) Successful in 16s
CI / format, compile, credo, dialyzer (pull_request) Successful in 1m47s
CI / tests (SQLite) (pull_request) Successful in 2m46s
CI / dependency audit (pull_request) Successful in 46s
CI / tests (PostgreSQL) (pull_request) Successful in 2m33s
8ec7b8eed6
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
White_Ghost_Cyber_Systems/traefik-control!42
No description provided.