Skip to content

Approval requests (four-eyes)

Some changes should not be made by one person alone. With approval requests, an operator who holds an action only with approval does not execute it: the console records a request, and an approver carries it out under their own rights. The typical MSP setup: the client's power users (AD operators) ask, the MSP technicians (Teleport operators) approve.

What can need an approval

Only changes an approver can carry out for the requester through the same single action:

Action Code
Password reset (the password is generated when the request is executed) users.password.reset
Unlock users.unlock
Disable / enable users.disable, users.enable
Attribute changes users.attributes.write
Group member add / remove groups.members.write

Reads, account creation, moves, renames, group creation and deletion and LAPS are never approvable. Requests work for a single action, a bulk action (executed as a bulk job after approval) and the leaver flow.

Configuring it

Per delegation (Delegations > role > delegation > Requires approval): tick the actions of the delegation that need an approval, choose the approvers (roles, for example "MSP technicians", and/or named operators) and the number of distinct approvals (1, or 2 for sensitive actions). No approver named: the instance approvers below decide.

Instance settings (Configuration > Approvals, settings.manage, step-up):

  • Request lifetime: a pending request expires after N days (1-90, default 7).
  • Always require approval for: these actions need an approval for everyone, the super administrator included. Off by default: otherwise the super administrator never needs an approval.
  • Instance approvers and approvals needed for those: they approve "always require" requests and requests whose delegation names no approver. Nobody named: the super administrators approve.

The baseline export carries the approval actions and counts of each delegation, not the approvers (roles and operators are not portable): the target console's instance approvers apply until approvers are chosen there.

How it works

  1. The operator tries the action. The engine refuses it with ApprovalRequired (the API answers approval_required): the console offers Request approval instead. The operator gives a reason (required) and the ticket (instance rule; always for a bulk password reset).
  2. The engine decision is recorded at request time (audited, like any decision): only actions held with approval become requests. A directly allowed action answers approval_not_required; any other refusal (scope, tier, VIP, own account...) stays a refusal: approvals never widen what the delegation covers. A request is never possible on one's own account. Parameters are captured without any secret (a reset's password is generated at execution time, a password typed in the request is ignored). Bulk requests capture the targets as explicit accounts.
  3. Approvers see it under Requests > To approve (and on the approval_requested notification). The requester can never approve their own request, whatever roles they hold; with two approvals, two different approvers are needed. The approvers are those captured with the request AND still named by the CURRENT settings (delegation approval option, instance approvers): an approver removed since then can no longer approve, and a raised approval count applies to pending requests.
  4. Approve needs a recent step-up (per the step-up policy). Before the approval is recorded, the approver must themselves be allowed the action directly (their own delegations; a delegation they hold only with approval does not count). The last approval executes at once under the approver's own rights: the same single action, decided again by the engine on fresh facts and audited under its normal name. The result is shown to both (Requests > My requests / History). A password reset's password is shown once to the approver (never stored, logged or audited). A bulk request runs as the approver's bulk job (its generated passwords are downloadable once by the approver).
  5. Reject needs a comment. The requester may cancel a pending request. Pending requests expire after the configured lifetime (checked every 5 minutes, Approvals:CheckInterval). A disabled requester's request cannot be approved.

Audit

Event When
request.created The request was recorded (attributes: action, request:<id>, approvals needed; reason: the decision at request time, e.g. approval_required:delegation:<id>) or refused (denied).
request.approved One approval (approval:1/2), or a refused approval (self_approval, reauth_required, approver_not_allowed:...).
request.rejected, request.cancelled, request.expired The request was closed without execution.
request.executed The execution's outcome (succeeded, partial, failed, ...).

request.approved, request.executed and the action's own rows (authz.decision, groups.members.add, users.password.reset, bulk.start with request:<id>, ...) share the approval's correlation id, so the audit log links a request to exactly what it changed.

Notifications

approval_requested (subscribe the approvers' channel) and approval_decided (subscribe the requesters' channel): see Notifications.

API

GET /api/approvals?view=mine|to_approve|history, GET /api/approvals/count, GET /api/approvals/{id}, POST /api/approvals (kind: action | bulk | leaver), POST /api/approvals/{id}/approve|reject|cancel, GET|PUT /api/admin/approvals/settings.