Just-In-Time Admin Elevation
Sensitive permissions — key rotation, audit export, role mutation, legal-hold release — should never sit on a session indefinitely. JIT elevation models the request → approval → time-boxed grant → auto-expiry lifecycle layered on top of standard RBAC, so an operator runs as their normal role 99% of the time and only borrows admin rights for the minutes they need.
State machine
Policy: enterprise vs government
ElevationPolicy is a four-knob struct. The default constructor returns enterprise defaults; ElevationPolicy::government() returns the FedRAMP-shaped preset.
| Knob | Enterprise default | Government preset |
|---|---|---|
| min_approvers | 1 | 2 |
| max_window | 60 minutes | 8 hours |
| forbid_self_approve | true | true |
| requires_reason | true | true |
Per-request grant windows are silently clamped down to max_window; the granted permission set can never widen beyond what the requester asked for. Both invariants are enforced inside ElevationService, not at the HTTP layer, so the SDK gets the same guarantees.
Tables
CREATE TABLE elevation_requests (
id UUID PRIMARY KEY,
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
requester_user_id UUID NOT NULL REFERENCES users(id),
requested_perms JSONB NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL,
decided_by_user_id UUID REFERENCES users(id),
decision TEXT CHECK (decision IN ('approved','denied','expired')),
decided_at TIMESTAMPTZ
);
CREATE TABLE elevation_grants (
id UUID PRIMARY KEY,
request_id UUID NOT NULL REFERENCES elevation_requests(id) ON DELETE CASCADE,
granted_perms JSONB NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ
); REST routes
All routes are mounted under /api/v1/admin/elevation.
| Method + path | Caller | Effect |
|---|---|---|
| POST /admin/elevation/request | Requester | Create request, returns id |
| GET /admin/elevation/pending | Approver | List requests awaiting decision |
| GET /admin/elevation/active | Anyone | List grants in their valid window |
| POST /admin/elevation/{id}/approve | Approver | Approves; emits grant when quorum reached |
| POST /admin/elevation/{id}/deny | Approver | Closes the request, no grant |
CLI
tetrapus-admin elevation request \
--perms "audit.export,users.delete" \
--reason "incident IR-2026-44 — exporting hold for counsel"
# 8c4a-f12d-…
tetrapus-admin elevation approve --id 8c4a-f12d-…
# approved GUI
Workspace → Profile → Org admin → Elevation tab. Two panels: my pending requests and requests awaiting my decision. Approving in the GUI calls the same /approve endpoint as the CLI.
Layering note for developers
The elevation core is intentionally pure types — no I/O, no DB, no async orchestration. It models requests, grants, policies, and a Clock trait you can mock in tests (SystemClock / MockClock). Persistence and quorum counting live in the server; the orchestration layer counts approvals and only calls ElevationService::approve once the policy quorum is satisfied.
Related
- – Permissions — the underlying RBAC surface
- – Audit Trail — every elevation step is audited
- – Compliance Matrix — IL4 access-control evidence
- – Back to Enterprise
Questions?
Reach out for help with integration, deployment, or custom domain codecs.