Time-bound privileged access — the PCSE §1.4 analog in Go
A short post on what we shipped this week:
pkg/auth/elevation— the Privileged Access Manager analog for an RBI FREE-AI-aligned Go platform. 430 lines of code, 430 lines of tests, every state transition in a hash-chained audit log.
The problem
Some operations need admin role for a short window: investigating a suspected breach, running an ad-hoc data migration, helping a customer with a rare edge case. Granting permanent admin to every engineer who might ever need that is the bad option — too many admins, too many compromised tokens become production-admin tokens.
The pattern that fixes it: request → approve → window-of-time → automatic expiry, with every transition in the audit chain. After expiry the role is gone; no daemon to forget to run, no cron to mis-fire. Lazy expiry checked on read.
The contract
- Engineer E requests elevation to
RoleAdminwith a TTL ≤MaxDurationand a non-emptyReason. - Approver A (must hold
RoleAdmin, must not be E) approves the request. With N-eye policy (RequireApprovers > 1), N distinct approvers must each call Approve. - The grant becomes Active with
ExpiresAt = approve_time + TTL. - Effective roles for E include the elevated role until
ExpiresAt. - After
ExpiresAtthe grant is treated as Expired on the next read. - Revocation by any admin is possible at any time before expiry.
The state machine
Request Approve(s reach N)
(nothing) ─────────► Pending ───────────────────────► Active
│ │
│ Deny │ Revoke (admin)
▼ ▼
Denied Revoked
past ExpiresAt
Active ───────────────► Expired
(lazy, on read)
All five terminal states are durable; nothing transitions back. A denied request can be re-submitted as a new request (new id).
Security properties locked in
| Property | Enforced by |
|---|---|
| Reason required on every request | ErrReasonRequired |
TTL capped at MaxDuration (default 4h) |
ErrTTLOutOfRange |
| Subject cannot approve own request | ErrApproverIsSubject |
| Same approver cannot count twice under N-eyes | ErrDuplicateApprover |
| Only admins can approve / deny / revoke | ErrApproverIneligible (router + service double-gate) |
| Audit chain entry on every transition (incl. lazy expire) | compliance.AuditLog.Append in every method |
| Audit-failure rolls back the in-memory state | Each method restores prior state on append error |
| Stranger Get returns 404, never 403 | Avoids existence confirmation |
Lazy expiry — no daemon
A grant past ExpiresAt transitions on the next Get/List/ActiveFor. The transition emits one elevation.expire entry exactly once (idempotent on subsequent reads). No background goroutine, no cron, no timer registration. The trade-off is that an active grant that nobody reads stays as Active in the in-memory map until someone reads it — but Active without an effective-role read changes nothing about authorisation, so it’s harmless.
Defence in depth at the handler boundary
The router gates /approve, /deny, /revoke, /list with mid.RequireRole(auth.RoleAdmin). The service also checks approver.HasRole(auth.RoleAdmin) on every transition. Either layer alone is sufficient. Both layers make regression noisy: a refactor that drops the router gate still gets a 403 from the service; a refactor that drops the service gate still gets a 403 from the router.
The cost
| Package implementation | 430 LoC |
| Unit tests | 22 tests |
| HTTP contract tests | 11 tests |
| HTTP routes added | 6 |
| FREE-AI rec served | Rec 14 (board policy), Rec 22 (audit) |
| PCSE bullet closed | §1.4 (Privileged Access Manager) |
Read the code
- Package:
pkg/auth/elevation/elevation.go - HTTP handler:
pkg/web/handlers/elevation.go - Tests:
pkg/auth/elevation/elevation_test.go - Per-package doc:
docs/packages/auth-elevation.md - PCSE map:
docs/gcp-pcse-mapping.md(Gap #1 now ✅)
What’s next
Three more PCSE gaps to close:
- Gap #2 — Format-preserving encryption FF3-1 (~400 LoC) for PII that must keep its shape (Aadhaar stays 12 digits)
- Gap #3 — SAML verifier (~600 LoC) for IdP federation
- Gap #4 — KEK rewrap background job (~150 LoC) to complete the rotation story
Source repo: github.com/c2siorg/genie.