Files
terdut-server/docs/single-sign-on.md
T
Niklas Ye 44b2eb2cc3 Rewrite the README as highlights with screenshots; move the detail into docs/
The README was 1,240 lines of reference material and still described a
SQLite quick start. It is now a short tour (highlights, screenshots of the
web UI, an accurate quick start against Postgres), and each topic has its
own page under docs/ with an index: deployment, configuration, Alertmanager,
incidents, notifications, escalation, dead man's switches, single sign-on,
web UI, API and development. SERVICE-ACCOUNTS.md is rewritten from a
proposal into a reference, and TEAM-LOOKUP.md is gone with the endpoint it
described. The "Upgrading to ..." sections for an unreleased product are
dropped.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-09 14:56:13 +02:00

6.1 KiB

Single sign-on (OIDC)

Signing in through an OpenID Connect provider, and mapping its groups to teams and administrators. Back to the README and the documentation index.

terdut can sign people in through any OpenID Connect provider; the examples use Authentik. Groups at the provider decide who may sign in, which teams they belong to and whether they administer the install, much as Grafana's OAuth role and org mapping does. Password login keeps working alongside it unless you turn it off.

At the provider, create an OAuth2/OpenID provider and an application for it: a confidential client, redirect URI <TERDUT_PUBLIC_URL>/api/oidc/callback, and the openid, profile and email scopes. The issuer is the application's, e.g. https://auth.example.com/application/o/terdut/. Then set:

TERDUT_PUBLIC_URL=https://terdut.example.com
TERDUT_OIDC_ISSUER=https://auth.example.com/application/o/terdut/
TERDUT_OIDC_CLIENT_ID=terdut
TERDUT_OIDC_CLIENT_SECRET=...
TERDUT_OIDC_ALLOWED_GROUPS=terdut-users,terdut-admins
TERDUT_OIDC_ADMIN_GROUP=terdut-admins

Which team a group grants is not server-wide config: each team names its own group(s), set by that team's own owner (or an administrator) from its Members tab, or PUT /api/teams/{teamID}/oidc-groups {"member_group":"sre","owner_group":"sre-leads"}. A team must already exist before a group can grant access to it — the sync never creates one.

The web UI's sign-in page shows a "Sign in with " button (a plain link to /api/oidc/login) above the password form, or instead of it when TERDUT_PASSWORD_LOGIN=false; it asks GET /api/auth/config what the server offers (password_login, oidc.enabled, oidc.name). A refused sign-in comes back to that page with the reason spelled out. Access the groups grant is badged SSO on the Team, Admin and per-user pages, with its edit and remove controls disabled, and the Account page does not offer to set a password nobody could use.

What a sign-in does

  1. Who. The provider's (issuer, subject) is the identity. The first time, a user is found by email — only when the provider marks it verified, or TERDUT_OIDC_TRUST_EMAIL is set — or created with no password. A username taken by somebody else gets a numeric suffix (alice-2). Username and email follow the provider at each sign-in. Authentik reports email_verified as false unless configured otherwise, so linking existing users usually needs TERDUT_OIDC_TRUST_EMAIL=true.
  2. Whether. With TERDUT_OIDC_ALLOWED_GROUPS set, somebody in none of them is refused and nothing is created.
  3. What. The administrator flag follows TERDUT_OIDC_ADMIN_GROUP. Team roles follow each team's own oidc_member_group/oidc_owner_group; where both of a team's groups match, the owner group wins.

Managed access. What the sync grants is marked as managed by single sign-on, and only that is ever changed by it. It is added at sign-in, and removed at the next sign-in after the group is gone, even if that leaves a team without an owner (an administrator can always repair a team) — the provider is the source of truth for what it grants, so the last-owner and last-administrator guards do not apply. Memberships and administrators added by hand are left alone; the exception is a hand-added member whose team's own group grants a higher role, who is raised and from then on managed. Editing managed access by hand (POST or DELETE on a team's members, revoking an SSO-granted administrator) is refused with 409, since the next sign-in would undo it.

Upgrading past migration 013: reconfigure every team's groups. TERDUT_OIDC_GROUP_MAPPINGS is gone, and the sync no longer creates a team by name. Group-to-team-role mapping is now each team's own setting — an owner sets it from the Members tab, or PUT /api/teams/{teamID}/oidc-groups. Until a team's owner does that, an OIDC-sourced membership in it is dropped at that user's next SSO sign-in, the same as any other loss of group access. Set every team's groups before affected users next sign in, to avoid a visible gap in access.

How fast changes arrive. Groups are read only at sign-in. A session made by an SSO sign-in has a hard ceiling (TERDUT_OIDC_SESSION_MAX_AGE, default 12h) that sliding never extends, so a change at the provider reaches terdut within that time. Password sessions are unaffected.

API keys are not revoked when somebody is removed at the provider. terdut holds no refresh token and never asks the provider again, so a person removed from every allowed group loses their sessions within TERDUT_OIDC_SESSION_MAX_AGE and cannot sign in again, but keeps any API key they made (the TUI and scripts use them) until an administrator disables the user in terdut.

Signing in from a terminal. A client with no browser of its own, such as the TUI over SSH, signs in with a device code, run by terdut itself so the terminal never talks to the provider:

  1. The terminal calls POST /api/oidc/device and shows the person a link (<TERDUT_PUBLIC_URL>/device?code=XXXX-XXXX) and the code.
  2. On any device the person opens the link, signs in (by the provider or by password, whatever the login page offers), sees the code and the account, and presses Approve. Only a browser session can approve; an API key cannot.
  3. The terminal polls POST /api/oidc/device/token every 5 seconds and is given the ordinary terdut_session cookie once. A person who signs in through the provider gets the same TERDUT_OIDC_SESSION_MAX_AGE ceiling on the terminal's session as on their browser's.

A login expires after 10 minutes. GET /api/auth/config reports device_login.

If the provider is down, terdut still starts (discovery is fetched on first use) and password login is the way in. With TERDUT_PASSWORD_LOGIN=false that way is closed: set it back to true. The first administrator comes from the bootstrap endpoint, and stays a manual administrator that no group can revoke; on an SSO-only install set bootstrap.enabled: false in the chart if you don't want that account, or keep it and never give it a password.