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;