From 2a25ec3dff52077b94cc375170650e0d240da188 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 14 Oct 2026 21:47:15 +0200 Subject: [PATCH] authelia: render the swarm apex's operator rules wherever authelia runs The apex's `operatorsOnly` pair was guarded on `deploy.swarm-controller.enable`, which is true only on the host that runs the controller and with it the swarm UI's vhost. When authelia runs on another host, that vhost still asks it, but the authelia host rendered no rule for the apex at all, so every session reached the swarm UI and its `/api/` through `default_policy = one_factor`. The pair now renders whenever the swarm domain is set, like the bao UI's pair. No vhost on a host that doesn't serve the apex asks about it, so the extra rules there are inert. authelia 4.39.20's `validate-config` accepts them on an authelia-only host's rendered access_control. module-eval-swarm-otel-identity gains a case on the existing `autheliaOnly` fixture (authelia without the controller): the apex has exactly the allow+deny pair, the rule list is closed, and an account in no group is refused `/api/agents` while `group:admins` passes. Fixes #5120 --- nix/host-modules/swarm-authelia.nix | 8 ++++--- nix/module-eval/swarm-otel-identity.nix | 29 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index dda1d29d..463dd855 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -1174,9 +1174,11 @@ in # reasoning for both halves lives. metricsRules ++ metricsStoreRules - # Also guarded on the domain being set: without it a null - # apex would render a rule matching the string "null". - ++ lib.optionals (deployCfg.swarm-controller.enable && swarmDomain != null) (operatorsOnly { + # The swarm UI on the apex. Not keyed to a deploy flag: its + # vhost is on the swarm controller's host, which need not + # be this one. Guarded on the domain being set: without it + # a null apex would render a rule matching the string "null". + ++ lib.optionals (swarmDomain != null) (operatorsOnly { domain = swarmDomain; }) # The store's browser UI. Not keyed to a deploy flag: its diff --git a/nix/module-eval/swarm-otel-identity.nix b/nix/module-eval/swarm-otel-identity.nix index ada466a1..eb2b49c4 100644 --- a/nix/module-eval/swarm-otel-identity.nix +++ b/nix/module-eval/swarm-otel-identity.nix @@ -670,6 +670,35 @@ let && policy "group:admins" ui "/" == "one_factor" && policy "user:nobody" "forge.t.local" "/" == "one_factor"; } + { + # Authelia on a host that runs no swarm controller, so the swarm UI's + # vhost is elsewhere. That vhost still asks this authelia, so the apex's + # pair renders here regardless. + name = "authelia confines the swarm UI to operators on a host without the swarm controller"; + ok = + let + h = autheliaOnly.services.hyperhive; + rules = autheliaRules autheliaOnly; + policy = autheliaPolicy autheliaOnly; + in + h.deploy.authelia.enable + && !h.deploy.swarm-controller.enable + && + lib.filter (r: r.domain == "t.local") rules == [ + { + domain = "t.local"; + subject = [ "group:admins" ]; + policy = "one_factor"; + } + { + domain = "t.local"; + policy = "deny"; + } + ] + && autheliaSubjectRulesClosed rules + && policy "user:nobody" "t.local" "/api/agents" == "deny" + && policy "group:admins" "t.local" "/api/agents" == "one_factor"; + } { # Every subject-scoped rule the host renders, read in order. The `elem` # clauses are the control for the predicate's vacuous case; the