From 9a406c2046755f6740f1a1d48802814c9538b49f Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 5 Aug 2026 23:30:41 +0200 Subject: [PATCH] feat(nix): authelia moves to auth., cookie domain with it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third of the three service moves, and the one that fails closed rather than loudly. The vhost name and the session cookie's domain move in the SAME commit because authelia validates `authelia_url` is a sub-domain of the cookie `domain` at STARTUP -- move only the vhost and it does not misbehave at first login, it refuses to boot. No migration pin, unlike the forge and matrix: nothing depends on the old name yet, so it moves outright. Also gives the container the authelia binary, so an operator with a shell can run `authelia crypto hash generate` for the users file. That fix was written for a PR that was closed and is tracked nowhere else. The accepted scope limit is recorded where the cookie is configured: a hive keeping a domain outside the swarm's tree does not receive this cookie, so SSO covers the swarm's services and not that hive's own dashboard until its domain moves too. ⚠️ Nothing resolves `auth.` yet. The hive gateway is deliberately NOT taught about it: swarm services get their vhost and their name on the HOST, not inside a per-hive proxy container. Both halves are the swarm-nginx work, and authelia has no vhost there either way today. --- nix/host-modules/swarm-authelia.nix | 60 ++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index f4f2ec87..2522d9c3 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -32,6 +32,7 @@ let cfg = config.services.hyperhive.swarm.authelia; hyperhiveCfg = config.services.hyperhive; hyperhiveDomain = hyperhiveCfg.domain; + swarmDomain = hyperhiveCfg.swarm.domain; # Upstream's `services.authelia.instances.` derives the unit, # user, group and StateDirectory from the instance name @@ -41,10 +42,16 @@ let unitName = "authelia-${instance}"; stateDir = "/var/lib/${unitName}"; - # Total on a null hive domain for the same reason the option defaults + # The SWARM's domain, because that is where the protected apps now live + # (`forge.`, `chat.`, `auth.`). It moves in the + # same commit as `domain` below and cannot lag it: authelia validates + # `authelia_url ⊂ cookie domain` at STARTUP, so a half-move does not + # misbehave at login — it refuses to boot. + # + # Total on a null swarm domain for the same reason the option defaults # below are: the required-domain assertion in hive-network.nix should # be what an operator sees, not a coercion error from here. - cookieDomain = if hyperhiveDomain == null then "invalid" else hyperhiveDomain; + cookieDomain = if swarmDomain == null then "invalid" else swarmDomain; in { options.services.hyperhive.swarm.authelia = { @@ -85,15 +92,25 @@ in domain = lib.mkOption { type = lib.types.str; - # Total on a null hive domain so the required-domain assertion in + # Under the SWARM domain, like the forge and matrix: a swarm has one + # SSO provider, and the session cookie has to reach the swarm's + # services. + # + # Total on a null swarm domain so the required-domain assertion in # hive-network.nix is the thing that fires; see the comment there. - default = if hyperhiveDomain == null then "auth.invalid" else "auth.${hyperhiveDomain}"; - defaultText = lib.literalExpression ''"auth.''${services.hyperhive.domain}"''; + default = if swarmDomain == null then "auth.invalid" else "auth.${swarmDomain}"; + defaultText = lib.literalExpression ''"auth.''${services.hyperhive.swarm.domain}"''; example = "login.example.com"; description = '' Public hostname for the SSO provider — the sub-domain shape the - forge and matrix already use. Doubles as the cookie domain's - host, so it must be the name browsers actually visit. + forge and matrix already use, under the swarm's domain because a + swarm has **one** SSO provider. Must be the name browsers + actually visit: it is the `authelia_url` the session cookie is + validated against. + + ⚠️ Unlike the forge and matrix names, this one carries **no + migration pin**: nothing depends on the previous + `auth.''${services.hyperhive.domain}` yet, so it moves outright. ''; }; @@ -148,6 +165,13 @@ in { system.stateVersion = "26.05"; + # The authelia binary itself, so an operator who gets a shell + # in here can run `authelia crypto hash generate` to make a + # password for the users file. Without it the container runs + # authelia and cannot invoke it: the unit's ExecStart resolves + # through the store path, and nothing puts the CLI on PATH. + environment.systemPackages = [ cfg.package ]; + # This container shares the host netns, so its own # firewall.service would rewrite the HOST ruleset at every # boot. The host firewall owns all filtering. @@ -241,14 +265,22 @@ in access_control.default_policy = "one_factor"; - # The cookie domain is the hive's domain, NOT authelia's + # The cookie domain is the SWARM's domain, NOT authelia's # own host: the session cookie has to be sent to the apps - # being protected (``, `forge.`, - # `matrix.`), and a cookie scoped to `auth.` - # reaches none of them. authelia enforces the relationship - # from the other side too — `authelia_url` must be a - # sub-domain of `domain`, so setting both to the same host - # fails validation at startup rather than at first login. + # being protected (`forge.`, `chat.`), and a + # cookie scoped to `auth.` reaches none of them. + # authelia enforces the relationship from the other side + # too — `authelia_url` must be a sub-domain of `domain`, so + # setting both to the same host fails validation at startup + # rather than at first login. + # + # ⚠️ Known and accepted consequence while a hive keeps a + # domain outside the swarm's tree: this cookie is NOT sent + # to that hive's own surfaces (its dashboard), so SSO + # covers the swarm's services and not the hive's. It + # resolves when the hive domain moves under the swarm + # domain; until then it is a scope limit, not a bug to + # chase. session.cookies = [ { domain = cookieDomain;