docs(sso): document the machine surface, and stop restating it in nix
`docs/swarm/sso.md` described a person in a browser. The swarm's other callers — the telemetry collector, the queue's auth-callout responder, each hive's agents — hold no session and follow no redirect, and nothing operator- facing said how they authenticate. Its relying-party table is forge and matrix, both browser surfaces. The new section carries what `swarm-authelia.nix` was holding in comments: one client per hive because identity belongs to the directory, the audience being that client id rather than a parallel naming scheme, and signed rather than opaque tokens because the collector verifies offline against `/jwks.json` while the queue introspects. It also states the fail-closed rule once, in the place a reader looks before touching a vhost: an error page answers 200, and `auth_request` reads any 2xx as access granted. That shape has now appeared three times — this module's `/api/` prefix and both of victorialogs' routes — which is what makes it documentation rather than a comment. The two comment blocks those replace shrink to the part that is genuinely local: the submodule-typing reason these clients are a definition rather than an append, and a loud warning against folding the machine prefix back into `/`. The security warning stays at the site; only its consequence list moves. Comments 495 -> 465 lines. Option `description` strings are untouched: they are the source `pkgs.nixosOptionsDoc` renders into the operator's options reference, so trimming one would delete published documentation rather than a duplicate.
This commit is contained in:
parent
f625151556
commit
9b14014077
2 changed files with 73 additions and 41 deletions
|
|
@ -177,6 +177,69 @@ Two consequences worth stating plainly:
|
|||
directly and authenticates itself. The forward-auth vhosts protect
|
||||
browser surfaces; this is not one of them.
|
||||
|
||||
## 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's own agents.
|
||||
|
||||
**One client per hive, not one per service.** A hive's identity belongs to
|
||||
the directory rather than to whichever service happens to consume it, so a
|
||||
hive holds a single OIDC client — `<hiveClientPrefix><hive>` — and mints a
|
||||
different token per service from it. The alternative, letting each
|
||||
consuming subsystem declare its own list, collides on the same client id
|
||||
the moment a second consumer appears.
|
||||
|
||||
**The audience is that client id.** A swarm service that has to tell hives
|
||||
apart needs one name both sides already agree on, and the client id is
|
||||
already that name. A parallel per-hive naming scheme would be a second
|
||||
thing to keep in step, and it drifts silently — a mismatch presents as a
|
||||
valid token refused at the target, which reads like a broken credential
|
||||
rather than a broken name.
|
||||
|
||||
**Tokens are signed (`RS256`), not opaque**, because a resource server
|
||||
that cannot call the provider back is a real case here: the telemetry
|
||||
collector verifies offline against `/jwks.json`, and an opaque token gives
|
||||
it nothing to verify. The queue's responder introspects instead — a
|
||||
different question asked of the same token, and the reason both
|
||||
`/api/oidc/introspection` and `/jwks.json` have to stay reachable.
|
||||
|
||||
### Machine callers must fail closed
|
||||
|
||||
⚠️ **An error page that answers `200` is a security bug, not a cosmetic
|
||||
one.** The browser surface intercepts upstream errors and serves a
|
||||
friendly "SSO is unavailable" page; that page is a file, so it returns
|
||||
`200`. Any machine caller routed through it receives a success carrying
|
||||
HTML instead of the failure that actually happened:
|
||||
|
||||
- `/api/authz/auth-request` — nginx `auth_request` treats **any 2xx as
|
||||
success**, so a down provider means *access granted*
|
||||
- `/api/oidc/introspection` — a token check that answers `200`
|
||||
- `/api/oidc/token`, `/.well-known/openid-configuration` — a client
|
||||
parsing an error page as its JSON document
|
||||
|
||||
So authelia's `/api/` and `/.well-known/` prefixes are routed **without**
|
||||
error interception. The split is by *audience*, not by an enumerated path
|
||||
list: a human gets the page, every machine caller gets the status.
|
||||
Enumerating endpoints individually would leave the next one added
|
||||
silently intercepted.
|
||||
|
||||
The same shape bites any machine route behind a browser-shaped gate: a
|
||||
`302` to a login page is followed, the login page answers `200`, and the
|
||||
caller reports success while nothing happened. Log ingest hit exactly this
|
||||
and lost eleven hours of delivery in silence.
|
||||
|
||||
**Checking it, if you change this routing.** Point the vhost at a dead
|
||||
upstream and compare three requests, not one:
|
||||
|
||||
1. through `/` — must still serve the friendly page
|
||||
2. through `/api/` — must deny
|
||||
3. a direct dial to authelia — must match what (2) did
|
||||
|
||||
All three matter. A change that silently deleted the browser page would
|
||||
pass a deny-only check, and one that quietly stopped denying would pass a
|
||||
page-only check. This was verified that way when the split was introduced.
|
||||
|
||||
## What this does not do
|
||||
|
||||
- **It does not disable local login.** Each service keeps its password
|
||||
|
|
|
|||
Loading…
Reference in a new issue