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.
177 lines
8.8 KiB
Markdown
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.
|