· 3 min read · ← All posts
Security Go Audit PAM RBAC

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

  1. Engineer E requests elevation to RoleAdmin with a TTL ≤ MaxDuration and a non-empty Reason.
  2. 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.
  3. The grant becomes Active with ExpiresAt = approve_time + TTL.
  4. Effective roles for E include the elevated role until ExpiresAt.
  5. After ExpiresAt the grant is treated as Expired on the next read.
  6. 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

What’s next

Three more PCSE gaps to close:

Source repo: github.com/c2siorg/genie.

← Back to all posts