# Sudo mode and four-eyes approvals

Part of [Security](/docs/guides/security/).

## Sudo mode

Destructive and credential-changing actions ask a console session to prove it is still its
operator. The session already proved the account's password and second factor at sign-in, so
confirming asks for **one factor**: `POST /api/auth/elevate` with a current TOTP or backup code,
or a passkey (`/api/auth/passkeys/elevate`); for the master token its second factor, or the token
itself when it has none. An account with no second factor cannot enter sudo mode. The console
shows a "Confirm it's you" dialog and retries the action.

Sudo mode is a window that stays open while it is used, as GitHub's does. It opens for
`auth.sudo.idle_minutes` (15) and every destructive action performed in it pushes the end out by
that much again, but never past `auth.sudo.max_minutes` (120) from the moment it was confirmed,
nor past the session's own end. Only an action that needed sudo mode extends it: reading the
console, or leaving a tab polling, does not. The end is written at most once a minute. A session
that was confirmed before this behaviour existed ends at the deadline it was given.

`auth.sudo.require_password: true` asks for the account's password together with the code,
as 3.3 did; the passkey is still a confirmation on its own. A wrong code is counted by the
account's lockout and the address's, exactly as at sign-in, and a TOTP step is accepted once for
sudo mode. Each confirmation is audited as `auth.elevate`, with what it was made with (never the
code), and a refusal as `auth.elevate` `failure`.

Under the `ens-medium` profile the window is 10 minutes idle and 30 in all, the password is always
asked with the code, and an operator may tighten these values but not loosen them (see
[ENS.md](/docs/guides/ens/)).

A central vouches for its operator's sudo mode to a node with `X-Noust-Elevated`, and reads the
window through the same `elevation_satisfied` the server uses for its own routes; a call the
node's schema marks as elevated passes through `ensure_elevated` on the central first, so working
on a node through a central keeps the central's window open. A bulk fleet job queued while
elevated keeps its elevated steps for 30 minutes whether or not the operator is still working.

Which routes need it is declared on each route and published in the OpenAPI schema as
`x-noust-requires-elevation`, so the console, a script and a central all read the same list. It
includes:

- deleting anything: an application, a database, a database user, a service, a site, a
  certificate, a backup, a backup schedule or destination, a cron job, a domain, an account;
- uninstalling a database engine; restoring an application or database backup; revoking a
  certificate;
- revealing a secret (an `.env` in clear, an application's database URL, a database password,
  a webhook secret, a destination's key) and writing an `.env`;
- moving an application to releases; changing its limits, health check, retention, branch,
  blue/green or previews;
- editing a unit or a site configuration by hand; creating a service; creating or rewriting a
  cron job or a backup schedule; building an application as root;
- editing, inserting or deleting a database row;
- writing Noust's configuration, whole or by section;
- server changes: updates, reboot and shutdown, storage clean-up, swap, time, host name, SSH,
  the firewall, fail2ban, accepted risks;
- managing accounts, invitations and separation-of-duties exceptions; issuing an API token;
  disabling 2FA; regenerating backup codes; deciding an approval;
- deploying or inspecting a local path (see [Local paths](/docs/guides/security/roles/#roles-and-permissions));
- `POST /api/jobs/delete`, which queues the same deletion `DELETE /api/apps/{domain}` does.

Sudo mode applies to sessions, whichever channel carries the session token: the cookie, or
`Authorization: Bearer` for a client that signed in with `bearer: true`. The master token and
API tokens issued by 3.0 are not asked to elevate. A token issued from 3.1 on is refused on these
routes unless it was issued with `allow_elevated`. The rule follows what the credential is,
never the header it arrived in.

## Four-eyes approvals

With `approval.enabled: true`, and always under the `ens-medium` profile, the changes that are
root on the host by another name need a second person: raw units, cron commands, backup hooks,
raw site configuration, deploy hooks, moving an application to its own account and building as
root (`root_equivalent`); SQL that writes, row edits
and `EXPLAIN ANALYZE` included; deploying from a directory on the server; adding and removing
fleet servers; opening a root shell on the server in the browser (see
[Terminal and one-off commands](/docs/guides/terminal/)); and role grants (a role change, a new account, a
recovery invitation). The OpenAPI schema marks those routes with `x-noust-requires-approval`.

- The first call does not run. It answers `202` with `approval_required` and becomes a request
  that snapshots the exact call (method, path, query and body, by their SHA-256), with a reason.
- By default only a `security` account decides. With `approval.approvers: [security, admin]`,
  an `admin` may approve infrastructure changes, never a role grant. Never the requester, never
  another account of the same person, never a token, the master token or a central. Root at the
  terminal decides too (`noust approval approve`), on record with its operating-system identity.
- A request waits `approval.request_hours` (24) for a decision. An approval lets its requester
  make that exact call once, within `approval.execute_minutes` (30), with
  `X-Noust-Approval: <id>`.
- Every step is an audit event naming both people.
