The question this answers is "what do I have to configure, and where" — so the table of secrets is the deliverable and the prose is scaffolding around it. The organising idea worth keeping: a secret belongs in-container when nothing outside that container reads it. Every one of authelia's own secrets passes that test; the client secret's plaintext fails it, which is what makes delivery a problem at all rather than a detail.
4 KiB
Swarm SSO
The swarm runs one authelia, and it is two things at once: the session
provider every protected vhost checks (auth_request), and — once any
client is declared — an OIDC provider issuing tokens to relying
parties like the forge.
The second role is derived rather than switched:
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.
What secrets exist, and where each one lives
| secret | generated by | rests in | read by |
|---|---|---|---|
jwt.key, session.key, storage-encryption.key |
authelia's first-boot unit | /var/lib/authelia-swarm/ |
authelia |
oidc-hmac.key |
same unit | same directory | authelia |
oidc-issuer.key (RSA) |
same unit | same directory | authelia signs with it; clients verify the public half at /jwks.json |
oidc-clients/<id>.digest |
same unit, via authelia crypto hash generate |
same directory, merged in through settingsFiles |
authelia |
oidc-clients/<id>.secret |
the same mint — this is its plaintext half | same directory | the relying party, in another container |
Everything above the last row is generated in-container because nothing outside that container ever reads it. That is the test worth applying to any secret added here. The last row fails it, and that is the entire reason a delivery step exists.
None of it is ever written into a nix expression. authelia's
settings are rendered into the nix store, which is world-readable and
permanent, so the client digest reaches authelia through settingsFiles
(merged at runtime) and every other secret through a *File option
carrying a path rather than a value.
Getting the plaintext to the relying party
Three cases, and they are genuinely different mechanisms rather than one mechanism with flags.
1. All-local — one host runs both
Nothing to configure beyond swarm.forge.sso.enable = true. A host-side
unit waits for authelia's first boot to mint the secret and copies it
into the forge container, and the forge module contributes its own client
entry — callback URL included — to authelia's client list.
The callback is built from the same source name the registration uses, so the redirect URI authelia is told to allow and the one forgejo actually sends cannot drift apart. A mismatch there is a rejected login with no error text worth reading.
⚠️ The delivery is a copy, not a bindMounts entry, and deliberately so:
nixos-container refuses to start a container whose bind source is
missing, and this secret does not exist until authelia's first boot has
run. Binding it would make the forge wait on a file that waits on a
container that starts after it — on a fresh hive, a permanent stall
presenting as "the forge is broken", several layers from its cause.
2. Swarm-managed services
The controller side owns provisioning: swarmctl writes both halves, the
same way it already owns authelia's user store (users.json canonical,
users.yml a rendered artifact).
3. A hive elsewhere
No shared host, so no automatic path. The operator provides the file and names it:
services.hyperhive.swarm = {
authelia.url = "https://auth.example.com";
forge.sso = {
enable = true;
clientSecretFile = "/var/lib/hyperhive/forge-oidc-secret";
};
};
Both are asserted at eval. A hive that boots with SSO half-configured shows a login button that always fails — a symptom several layers from its cause, and far worse to diagnose than an evaluation error.
What this does not do
- It does not disable local login. The forge keeps its password database and gains a second door. An identity provider that can take the forge offline when it hiccups is a worse forge than one with two ways in.
- It does not provision users. Agents are created and destroyed
continuously, so the subject set belongs to a program rather than to a
config file; today that program is
swarmctl.