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

177 lines
8.8 KiB
Markdown

# 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`](../../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: <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`](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: <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`](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,
`<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`](../../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://<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`](../../../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/<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`](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.