Skip to main content

Approval workflow

A four-eyes gate on top of RBAC: when a gate is on, operators don't execute the operation directly — they file a request that a project admin approves, and only then does it run. This page deepens the approval overview.

What can be gated

Five independent toggles under Settings → General → Approval Requirements (org-wide, stored in general settings):

ToggleSettingGates
Issuerequire_approval_for_issueNew certificate requests
Reissuerequire_approval_for_reissueReissue with new parameters / key rotation
Renewrequire_approval_for_renewManual renewals
Revokerequire_approval_for_revokeRevocation at the CA
Downloadrequire_approval_for_downloadPrivate-key export

All gates are off by default. Scheduler-driven auto-renewals are not affected — the gates sit on the API operations.

Lifecycle

StatusMeaning
pending_approvalAwaiting a reviewer.
approvedAccepted — waiting for an explicit Execute.
executingOperation in flight.
executedRan successfully (job outcome tracked on the job itself).
execution_failedOperation errored; retryable failures can go back to approved.
rejectedDeclined with a reason.
cancelledWithdrawn by the requester or an admin before execution.
expiredNot completed within 7 days of creation.

Only one open request may exist per certificate + operation — a duplicate submit is rejected until the first request reaches a terminal state.

Who approves — and who bypasses

  • Approve / reject requires the project admin role (or higher) on the request's project.
  • Self-approval is blocked at the service layer — the approver cannot be the requester ("cannot approve your own request").
  • Admins and owners auto-approve: when a privileged user submits a gated operation, the request is created directly in approved (with themselves recorded as approver) — they still go through the request/execute flow, so the audit trail is uniform, but no second person is required.

Net effect: the gate adds a second pair of eyes for operator-initiated actions; admin/owner actions are recorded but not blocked.

Execution

Approval does not auto-run the operation. After a request reaches approved, the requester or a project admin clicks Execute (or calls the execute endpoint). Execution:

  • enqueues the underlying job for issue / reissue / renew / revoke (the job ID is linked from the request), or
  • mints a one-time download token for download requests.

If execution fails, the request lands in execution_failed with an error code. The UI offers Retry for transient codes (JOB_QUEUE_ERROR, CA_ERROR, STATUS_CONFLICT); retry moves the request back to approved and executes again — no re-approval needed.

Expiry

Every request gets a fixed 7-day TTL at creation (not configurable). There is no background sweep — expiry is enforced lazily: any approve / reject / execute / cancel attempt on an overdue request atomically flips it to expired, emits approval.expired, and fails the attempted action. Expired requests never execute; re-submit if still needed.

Approved downloads

Private-key export through the gate ends in a token exchange rather than a direct file response:

  1. Requester asks to download; a download approval request is created (or auto-approved for admins).
  2. A project admin approves.
  3. Requester (or admin) executes — the response contains a raw download token.
  4. The browser fetches GET /api/v1/downloads/{token} immediately.

Token properties: 60-second TTL, single use (atomically consumed), bound to the executing user, and stored only as a SHA-256 hash. Expired, reused, or wrong-user tokens all fail identically. See Certificate download for formats.

Preflight warnings

Independent of the approval gates, MSCA issuance runs preflight checks and can pause submission with warnings that the requester must explicitly confirm (re-submit with the confirmation flag):

  • template cache older than 30 days — consider syncing templates,
  • connection not validated in over 24 hours,
  • credentials changed after the last successful authentication.

Other issuer types skip preflight. The confirmation is part of the request, so approvers review a warned-and-acknowledged submission.

My Requests page

My Requests in the main navigation:

  • My Requests view — everything you submitted, filterable by status (All / Pending / Approved / Rejected / Executed / Failed / Expired / Cancelled). Rows expand to show reason, reviewer, rejection comment, and execution errors.
  • Pending Reviews view (project admins only) — every pending_approval request you can act on, with a live count badge.

Actions offered per status (the API additionally lets project admins execute / retry / cancel — see endpoints):

StatusRequester (My Requests)Project admin (Pending Reviews)
pending_approvalCancelApprove · Reject · Cancel
approvedExecute · Cancelview only
execution_failedRetry (if retryable) + error detailview only
executedLink to the resulting jobview only

Notifications

Route these through notification rules:

EventFires when
approval.requestedA request enters pending_approval (not emitted for auto-approved requests).
approval.approvedA request is approved — including admin auto-approval at submit time.
approval.rejectedA reviewer rejects.
approval.executedExecution completed successfully.
approval.expiredA stale request is flipped to expired.

Payloads carry operation_type, primary_domain, and the requester / reviewer display names.

API endpoints

All under /api/v1, authenticated. The action endpoints are rate-limited to 10 approval actions per user per minute.

MethodPathWho
GET/approval-requests/myAny user — own requests
GET/approval-requests/:idAny user in the org
POST/approval-requests/:id/approveProject admin (not the requester)
POST/approval-requests/:id/rejectProject admin
POST/approval-requests/:id/executeRequester or project admin
POST/approval-requests/:id/retryRequester or project admin
POST/approval-requests/:id/cancelRequester or project admin
GET/downloads/:tokenHolder of a valid, unused token (user-bound)
GET/projects/:projectId/approval-requestsProject admin — review queue

Troubleshooting

"an open request already exists for this certificate"

One open request per certificate + operation. Find it on My Requests and cancel or complete it first.

Approved, but nothing happened

Approval doesn't execute. Open My Requests, find the approved row, click Execute.

"cannot approve your own request"

Separation of duties — another project admin must review it. (If you're an admin submitting your own operations, they auto-approve at submit time instead.)

Download token "expired" seconds after execute

The token lives 60 seconds and works once, for the user who executed. Execute again to mint a fresh token and let the browser follow it immediately.

See also