# Swarm SSO The swarm runs one authelia, and it's two things at once: the **session provider** every protected vhost checks (`auth_request`), and — once an operator declares any client — an **OIDC provider** issuing tokens to relying parties: the forge and the matrix homeserver. The second role derives rather than switches: `services.hyperhive.swarm.authelia.oidc.clients` being non-empty turns it on. authelia refuses to start with a provider that has no clients, so a separate `enable` would be a second fact free to disagree with the first. ## Getting in the first time authelia binds no TCP port: its listeners are unix sockets only the gateway can reach. The **gateway** on the host running it publishes it as `auth.` — vhost, dnsmasq record and TLS name all follow `deploy.authelia`, so there is nothing to turn on separately. (Details, including why a client hive must not declare that vhost: [`4-implementation/networking/gateway.md`](../../4-implementation/networking/gateway.md).) **Nobody can log in until the first user exists.** authelia exits at startup on an empty user set: ``` error reading the authentication database: could not validate the schema: users: non zero value required ``` While the set is empty this module writes one placeholder subject, `swarm.placeholder`, that authelia accepts and that can't log in: it's `disabled: true`, and its password digest has an all-zero key that no known password hashes to. authelia starts and serves its login page, and refuses every login. The placeholder stays in `users.yml` once real users exist, shows up in `swarmctl user list`, and `swarmctl user add` refuses its name. ⚠️ The step below is **required to finish the install**, not an optional first-login convenience. Add the first subject on the host running authelia: ```console # swarmctl user add mara --display-name Mara --email mara@example.com --group admins added mara to /var/lib/authelia-swarm/users.yml password: this password is stored nowhere — record it now ``` `swarmctl` generates, hashes, and prints the password once, keeping only the hash. It reads and writes authelia's `users.yml` directly — it's the one user store, shared with `swarm-authelia-bridge`, which creates agent identities in the same file. No restart: authelia watches it. Full reference: [`2-handbook/swarm/swarmctl-cli.md`](swarmctl-cli.md). You can edit `users.yml` by hand, and `swarmctl` will read what you wrote. ⚠️ It rewrites the whole file on every change, so **comments and formatting don't survive**; values and unrecognised keys do. This step stays manual on purpose. Bootstrapping an identity provider non-interactively means a secret arriving from somewhere — a file, an env var, a nix expression — and every one of those is worse than an operator typing one command once. ### Changing a subject afterwards `user add` only ever adds: on a name that already exists it refuses, rather than resurfacing as a second account or a silent overwrite. Editing an existing subject is `user update`, and the flags compose, so one call can change multiple things: ```console # swarmctl user update mara --add-group admins --email mara@example.com added to group "admins" email: unset -> "mara@example.com" mara is now in groups: admins ``` Two behaviours worth knowing before you rely on them: - **`--remove-group` fails if the user isn't in that group.** Every other flag is idempotent — setting what's already set is fine, so a "make these four things true" call doesn't break when one of them already was. Revocation is the exception on purpose: a typo'd group name that reported success would leave an account holding access you believe you took away, and that's the one outcome nobody re-checks. - **`swarmctl` prints the resulting group list** because group names have no registry anywhere. A misspelled `--add-group` creates a real group that no access-control rule mentions, so the user gains nothing and no error is possible — reading the line back is the only check there is. Passwords are deliberately out of scope here: regenerating a credential is a different intent from editing an attribute, and folding them means an attribute edit can invalidate a login by accident. Resetting one is a separate verb, `user reset-password`, which generates and hashes a new password the same way `user add` does and refuses on an unknown user: ```console # swarmctl user reset-password mara reset password for mara in /var/lib/authelia-swarm/users.yml password: this password is stored nowhere — record it now ``` Authelia's file store has no SMTP notifier, so this is the only way a human account gets a new password once an operator forgets the old one. ## Logging in to the forge and matrix - **A person's first forge login creates their forge account**, named after their authelia username, provided the subject has an email. It starts as an ordinary user; `swarmctl forge make-admin ` makes it a site admin. A name that already has a local forge account isn't handed over: forgejo asks for that account's own password first. Agents, `core` and `swarm-controller` all have local accounts. - **A member of `admins` becomes a matrix homeserver admin** once their first matrix login has created the account. `swarm-controller` promotes them within five minutes, and never demotes. See [`matrix.md`](matrix.md#operators-are-homeserver-admins). You place no client secret by hand. Authelia mints each one at its first boot and the host-side units deliver it to the forge and the homeserver, retrying until it lands, so there is no start order to get right. A forge on another host than the store needs the forge publisher's store leaf, in `deploy.bao.forgePublisherClientCertFile` and `forgePublisherClientKeyFile`. Without it the forge has no login source, and the unit fails naming both options. Where each secret lives, the delivery mechanics and how forge and matrix differ: [`sso.md`](../../4-implementation/swarm/sso.md). ## Machine clients Everything above is a person in a browser. A swarm also has callers that hold no session and follow no redirect: the telemetry collector, the queue's auth-callout responder, and each hive. A hive holds a single OIDC client, ``, and mints a different token per service from it. The gateway answers machine callers with the real status rather than the friendly "SSO is unavailable" page, so a down provider denies access instead of granting it. The reasoning and how to check it after a routing change: [`sso.md`](../../4-implementation/swarm/sso.md#machine-callers-must-fail-closed). ## Operator CLI login `swarmctl login` logs an operator in as their own account, from any machine, through the public `swarmctl` client authelia always registers: no secret, authorization code with PKCE, a loopback redirect, and a refresh token. Its access token carries `authelia.bearer.authz` with the audience `https:///api/`, so the gateway admits it at the controller's API and nowhere else, under the same access rule as a browser session there. Revocation is per account: once authelia stops accepting the account, the gateway refuses its token too. See [`swarmctl/README.md`](../../../swarmctl/README.md#remote-use-swarmctl-login). ## What this doesn't do - **It doesn't disable local login — except on Grafana.** Forgejo and tuwunel keep their password databases and gain a second door: an identity provider that can take a service offline when it hiccups is worse than one with two ways in, and making authelia the only path there is a separate, reversible switch (tuwunel's `login_with_password`, forgejo's own setting). ⚠️ **Grafana is the exception, and not a reversible one.** Its module sets `auth.disable_login_form` unconditionally: its vhost is on the public gateway, so a password box there would be a way in whatever the deployment. The form is all this hides. Grafana's API still takes HTTP basic auth for the built-in `admin` account, whose password is a random one minted into the swarm secret store, not the upstream `admin`. A dead provider locks out the SSO accounts but not that one: an operator reads the password from `swarm/services//admin/password` in the store (or, as root on the Grafana host, from `/run/swarm-grafana-secrets/admin-password`) and calls the API with it. See [`services.md`](services.md#metrics-victoriametrics--grafana) and [`credentials.md`](credentials.md). SSO logins are the everyday way in, which is why `swarm.grafana.oidc.role` defaults to `Admin`. The three secrets Grafana reads live on the host's tmpfs, so after a reboot Grafana stays down (no login, no dashboards) until the secret store answers; the delivery units retry and start it with no manual step. - **It doesn't provision users.** `swarmctl` creates and destroys agents continuously today, so the subject set belongs to a program rather than to a config file.