Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/2-handbook/swarm/sso.md
iris 0f39bdaaa4 docs: the forge, knowledge, archetypes, matrix and SSO pages move their implementation detail out
Account minting, knowledge sync, the archetype refresh, the matrix appservice
flow and the SSO secret delivery move to pages under 4-implementation/swarm.
Each handbook page keeps what an operator does or sees, with a pointer.
2026-11-11 01:47:20 +01:00

8.8 KiB

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.<swarm.domain> — 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.)

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:

# swarmctl user add mara --display-name Mara --email mara@example.com --group admins
added mara to /var/lib/authelia-swarm/users.yml
password: <generated>
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.

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:

# 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:

# swarmctl user reset-password mara
reset password for mara in /var/lib/authelia-swarm/users.yml
password: <generated>
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 <name> 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.

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.

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, <hiveClientPrefix><hive>, 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.

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://<swarm>/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.

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/<grafana client id>/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 and 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.