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