Just-in-time elevation
Eligible bindings that people activate for a bounded time, with justification, MFA, approval, approver groups, and tenant-wide floors.
Administrator and production roles are dangerous to hand out permanently. A stolen session of someone who holds them all the time is a stolen administrator. But taking the roles away entirely means a ticket and a wait every time someone needs them, so in practice they stay granted.
Just-in-time elevation removes that trade-off. You give people an : a record that they may take a role, which grants nothing by itself. When they need the role, they activate the binding for a limited time (an ), optionally stating a reason, proving MFA, or waiting for a second person to approve. When the time runs out, the role is gone. Nobody carries the privilege around, and every elevation leaves a record with a reason.
Make a role eligible
Create a binding as usual and add eligible: true plus the rules each activation must satisfy. The subject can
be a person or a group; with a group, every live member may activate it.
// The on-call group may take the incident-response role for up to two hours, with a reason and MFA.
await iam.api.bindings.create(credential, {
tenantId,
roleId: responder.id,
subjectType: 'group',
subjectId: onCall.id,
eligible: true,
maxActivationMs: 2 * 3_600_000,
requireJustification: true,
requireMfa: true,
});
// Production administration also needs a second person: the platform team approves.
await iam.api.bindings.create(credential, {
tenantId,
roleId: productionAdmin.id,
subjectType: 'group',
subjectId: engineers.id,
eligible: true,
requireApproval: true,
approverGroupId: platformTeam.id,
});Prop
Type
Activation settings only make sense on eligible bindings: passing them for a standing binding fails with
INVALID_INPUT (explicit false flags are accepted, so forms can send every checkbox). Use bindings.update to
change the rules later. bindings.update({ eligible: false }) turns the binding back into a standing one, clears
its activation settings, and ends every activation of it.
Activate
When a member needs the role, they call bindings.activate with the binding, how long they need it, and a reason.
The result is an activation record; while it is live, the role applies.
const activation = await iam.api.bindings.activate(memberCredential, {
tenantId,
bindingId: eligibleBinding.id,
justification: 'INC-4211',
durationMs: 30 * 60_000,
});
// activation: { id, status: 'active', active: true, activatedAt, expiresAt, ... }
// Done early? Step down so the role stops applying now.
await iam.api.bindings.deactivate(memberCredential, { tenantId, activationId: activation.id });bindings.activate checks, in order of what most often goes wrong:
- Permission. The caller needs
iam:bindings:activateon the role (iam/{roleId}), so a tenant can scope which roles a person may activate. - Session. The call must come from the person's own ordinary session of the tenant. Assumed-role sessions are
refused with
INVALID_INPUT, and an administrator using to view the product as a member cannot activate for them (IMPERSONATION_RESTRICTED). - Binding. The binding must apply to the caller, directly or through a group they belong to (
ACCESS_DENIEDotherwise), and must still be eligible and live (INVALID_TRANSITION). - Duration.
durationMsdefaults to the effective maximum. It must lie between one minute and that maximum; a longer or shorter value fails withINVALID_INPUTrather than being shortened. - Rules. Without a justification when one is required, the call fails with
INVALID_INPUT(justifications are at most 2048 characters). Without MFA when it is required, it fails withMFA_REQUIRED. - One at a time. There is one live activation per binding and person. Activating again while one is live, or
while a request is still waiting, fails with
CONFLICT(409).
The result carries status (active, pending for a request awaiting approval, or denied), active (whether
it grants right now), activatedAt, expiresAt, and the justification.
While an activation is live
The role behaves exactly like a standing binding. Authorization decisions, principal.roles in policy
conditions, reviews such as whoCan and effectiveActions, and identities.listBindings (which shows the
activation) all see it.
How an activation ends
An activation stops granting at the next request when any of these happens:
- It reaches its
expiresAt. This is the normal case: nobody has to do anything. - The holder calls
bindings.deactivate. Use it to step down when the work is done early. - An administrator calls
bindings.revokeActivation. Use it for incident response, when someone's elevated access must end now. It needsiam:bindings:deleteon the binding and the binding's own (or root), like deleting the binding. - The holder leaves the group, the binding or role is deleted, or eligibility is turned off.
The purge worker deletes ended activations from storage later; they grant nothing in the meantime.
Approval-gated activation
For the most sensitive roles, a reason and MFA are not enough: you want a second person to agree before anyone
elevates. This is two-person control. With requireApproval, bindings.activate records a request instead of
activating, and the role becomes live only when an approver says yes.
const pending = await iam.api.bindings.activate(engineerCredential, {
tenantId,
bindingId,
justification: 'CHG-88',
});
// The approver sees what awaits them and decides, optionally for a different duration.
const queue = await iam.api.bindings.listApprovals(platformCredential, { tenantId });
await iam.api.bindings.approveActivation(platformCredential, {
tenantId,
activationId: pending.id,
durationMs: 45 * 60_000,
note: 'Approved for the change window',
});The calls involved:
bindings.activatewithrequireApprovalreturns a request withstatus: 'pending'. It lapses after the tenant'sapprovalLifetimeMs(24 hours by default) if nobody decides.bindings.listApprovalsshows an approver the requests they may decide on, never their own. It powers an approver's inbox and needsiam:bindings:approveon the tenant.bindings.approveActivation({ activationId, durationMs?, note? })grants a request. The role is live from that moment for the requested duration, or for anotherdurationMsthe approver chooses, from one minute up to the binding's effective maximum.bindings.denyActivation({ activationId, note? })refuses a request, with an optional note explaining why.bindings.deactivatelets the requester withdraw their own request. It is audited asbinding:deactivatewithcancelled: true.
Both decisions need iam:bindings:approve on the role. Notes are at most 2048 characters. The safeguards:
- Nobody decides on their own request (
INVALID_INPUT). - Nobody decides from an impersonation session (
IMPERSONATION_RESTRICTED), so an administrator viewing the product as an approver cannot approve in their name. - Only a request that is still waiting can be decided, and only while its binding is still eligible
(
INVALID_TRANSITION, 409).
Members see their own waiting request as pendingActivation in bindings.listMine. Administrators list requests
and refusals with bindings.listActivations({ status: 'pending' }) or ({ status: 'denied' }).
Approver groups and managers
By default, anyone holding iam:bindings:approve on the role may decide. Name the approvers when only specific
people should:
approverGroupId: only live members of that group, or a root administrator, may decide. Use it for a platform or security team that owns production access.managerApproval: the requester's manager (Identity.managerId) may decide as well. Use it when the person who knows the requester's work should sign off.
The approvers named this way are emailed each request; the requester is never among them. A request that names
approvers must reach at least one active one. When the approver group is empty and the requester has no active
manager, activation fails with INVALID_TRANSITION (409) instead of creating a request nobody can answer.
Managers are set with managerId on identities.create or identities.update, and SCIM can maintain it.
identities.listReports lists a manager's reports, and offboarding hands a leaver's reports to the successor.
Emails
When the deployment sends email, two templates keep people informed without them watching a console:
activation-requestgoes to each approver when a request is recorded, so they can decide promptly.activation-decidedgoes to the requester when a request is approved or denied, so they know whether to start working.
Both carry the activation, role, requester, justification, and decision, so your application can render them however it likes.
Tenant access policy
Setting the same rules on every eligible binding is repetitive, and one forgotten binding is a gap. The sets minimum rules, or floors, for every eligible binding of an organization at once. A binding may be stricter than the policy, never looser, so adopting a floor later tightens existing bindings without editing them.
tenants.setAccessPolicy sets it (console: "Elevation defaults" on the Configuration page):
await iam.api.tenants.setAccessPolicy(credential, {
tenantId,
accessPolicy: {
maxActivationMs: 4 * 3_600_000,
requireJustification: true,
requireMfa: true,
approvalLifetimeMs: 8 * 3_600_000,
},
});
// accessPolicy: null clears the policy.Prop
Type
The effective rules for a binding are its own settings tightened by the policy: a flag is on when either sets it,
and the maximum length is the smaller of the two. Unknown fields are refused. Setting the policy needs
iam:tenants:update and recent authentication, and is audited as tenant:access-policy. The policy can also live
in a configuration document as accessPolicy.
Who may activate and approve
Activation and approval are ordinary permissions, so you decide who elevates and who approves with roles:
iam:bindings:activateoniam/{roleId}lets a member activate an eligible binding of that role. On*it lets them activate any role they are eligible for, and on the tenant it also allowsbindings.listMine.iam:bindings:approveoniam/{roleId}lets a person decide requests for that role. On the tenant it also allowsbindings.listApprovals.
The usual shape is a Member role bound to a group every person belongs to, and an Approver role bound to the approver group:
const member = await iam.api.roles.create(credential, {
tenantId,
name: 'Member',
permissions: ['iam:bindings:activate'],
});
await iam.api.bindings.create(credential, {
tenantId,
roleId: member.id,
subjectType: 'group',
subjectId: everyone.id,
});
const approver = await iam.api.roles.create(credential, {
tenantId,
name: 'Approver',
permissions: ['iam:bindings:approve'],
});
await iam.api.bindings.create(credential, {
tenantId,
roleId: approver.id,
subjectType: 'group',
subjectId: platformTeam.id,
});Because a member cannot approve their own request and the binding can name the approver group, approval gives you two-person control for the roles that need it.
Views for members and administrators
Each audience has a read that shows exactly what it needs:
| Call | Who | What it shows, and when to use it |
|---|---|---|
bindings.listMine | Members | Their own bindings, standing and eligible, with the live activation and any waiting request. Build an "Elevate" screen from it. |
bindings.listApprovals | Approvers | Waiting requests they may decide on, with the role and requester. Build an approvals inbox from it. |
bindings.listActivations | Administrators | Live activations by default; requests or refusals with status; ended ones with includeExpired. Use it to see who is elevated right now. |
bindings.list | Administrators | Bindings; eligible: true or false separates eligible from standing ones. Use it to audit which roles are standing. |
identities.listBindings | Administrators | One person's effective bindings with activation, pendingActivation, and inWindow. Use it on a member's detail page. |
bindings.listMine needs no iam:bindings:read, only iam:bindings:activate on the tenant, so members can see
what they may elevate to without seeing everyone else's access. It must be called from an ordinary session of the
tenant. The console's Elevate page is built this way: eligible roles with their rules, the activation form,
pending requests, and, for approvers, the requests awaiting their decision.
Audit and alerting
Every step is recorded in the audit log, so you can alert on elevation as it happens and answer "who was an administrator on Tuesday, and why?" later:
| Event | What happened, and why you would care |
|---|---|
iam:bindings:activate | The activation call ran. It is the operation record every API call leaves. |
binding:activate | A member activated a binding and now holds the role, with activationId, roleId, expiresAt, and the justification. Alert on it to see every elevation. |
binding:activation-requested | Activation needs approval and a request was recorded. Page approvers or open a ticket. |
binding:activation-approved | An approver granted a request. Keep it as evidence of two-person control. |
binding:activation-denied | An approver refused a request. Tell the requester, or watch for repeated refusals. |
binding:deactivate | An activation ended early: cancelled: true for a withdrawn request, revoked: true when an administrator ended it, which often means incident response. |
Subscribe a to binding:* to alert on every elevation, or query the audit log
for a member's history.
The member page in the console shows it as "Activation history".
await iam.api.webhooks.create(credential, {
tenantId,
url: 'https://ops.example.com/hooks/iam',
events: ['binding:*'],
});The access analysis points out where eligibility would help. It reports people with a direct, permanent binding
to a full administration role (standing-privileged-access) and eligible bindings nobody activated within the
dormancy window (unused-eligible-binding), which may not be needed at all. See
access reviews.
Was this page helpful?
Last updated on
Privileged access
Keep standing privilege low and access time-bound with eligible roles, expiring identities, access packages, reports, and configuration as code.
Access lifecycle
Time-bound identities, temporary memberships, future-dated bindings, API key hygiene, and offboarding that removes all access in one call.