From d337fec56567a963a568d6245548a784a4d214c4 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 12 Aug 2026 17:31:30 +0200 Subject: [PATCH] feat(3167): authelia gates the swarm UI on an operators group default_policy = one_factor means 'any authenticated user', which is authentication and not authorisation. The swarm UI is operator-only and agents are getting authelia accounts of their own, so a session alone would be enough to open it the day that lands - the vhost's auth_request would be a check nobody fails. Adds an access_control rule for the UI's domain requiring subject = group:operators, present only when the UI is enabled. The group name is a constant beside the rule: it is also what an operator types into 'swarmctl user add --group', and a configurable name is one more way for the rule and the account to disagree silently. --- nix/host-modules/swarm-authelia.nix | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 8aca2d44..b314cd53 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -37,6 +37,13 @@ let hyperhiveCfg = config.services.hyperhive; hyperhiveDomain = hyperhiveCfg.domain; swarmDomain = hyperhiveCfg.swarm.domain; + uiCfg = hyperhiveCfg.swarm.ui; + + # Group an account must hold to reach operator-only surfaces. Named + # here because this module writes the rule that enforces it and + # `swarmctl user add --group ` is what grants it — the two must + # agree, and one constant is how they stay agreeing. + operatorGroup = "operators"; # Upstream's `services.authelia.instances.` derives the unit, # user, group and StateDirectory from the instance name @@ -536,7 +543,26 @@ in authentication_backend.file.path = cfg.usersFile; - access_control.default_policy = "one_factor"; + # ⚠️ `one_factor` as the DEFAULT means "any authenticated + # user", which is authentication, not authorisation. The + # swarm UI is operator-only, and agents are getting + # authelia accounts of their own — so the day that lands, + # a session alone would be enough to open it. The rule + # below is what makes the distinction real; without it + # the vhost's `auth_request` is a check nobody fails. + # + # The group is a constant rather than an option: it is the + # value an operator types into `swarmctl user add --group`, + # and a configurable name is one more way for the rule and + # the account to disagree silently. + access_control = { + default_policy = "one_factor"; + rules = lib.optional uiCfg.enable { + domain = uiCfg.domain; + subject = [ "group:${operatorGroup}" ]; + policy = "one_factor"; + }; + }; # The cookie domain is the SWARM's domain, NOT authelia's # own host: the session cookie has to be sent to the apps