Watch
0
0
Fork
You've already forked hyperhive
0

authelia: a user outside a rule's group is denied instead of falling through to one_factor

authelia takes the first matching rule, and a rule with a `subject` does
not match a subject outside it. The swarm domain's and the bao UI's
`group:admins` rules had nothing after them, so an account in no group
skipped them and landed on `default_policy = one_factor`: any
authenticated account reached the swarm UI, its `/api/` (by cookie or by
bearer) and the bao UI.

Each of the two group rules is now an `operatorsOnly` pair: the group
allow, then a subject-less `deny` for the same domain, the shape
`collectorOnly` already gives the collector's metrics rules.
`default_policy` stays `one_factor`, which the forge and chat rely on.

module-eval-swarm-otel-identity gains two cases on `allLocal`: an
account in no group is refused the swarm domain (four paths) and the bao
UI while `group:admins` passes and the forge stays open; and a
predicate, `autheliaSubjectRulesClosed` in lib.nix, holds over the
rendered list: every subject-scoped allow is closed by a later
subject-less `deny` on its domain with no subject-less allow in between,
and nothing follows a domain-wide `deny` on its domain.

Fixes #5120
This commit is contained in:
atlas 2026-10-14 21:27:13 +02:00 • committed by mara
commit 4a9acd1d0c
4 changed files with 194 additions and 15 deletions

View file

@ -43,6 +43,21 @@ let
# failing the check it was supposed to pass.
operatorGroup = "admins";
# `target`, for `operatorGroup` and nobody else. The `deny` is what makes
# the group rule exclusive, for the reason `collectorOnly` gives: a subject
# outside the group skips the first rule and would otherwise land on
# `default_policy`.
operatorsOnly = target: [
(
target
// {
policy = "one_factor";
subject = [ "group:${operatorGroup}" ];
}
)
(target // { policy = "deny"; })
];
# Whether the swarm collector's client is actually registered here.
#
# Asked of the client list rather than re-derived from the conditions
@ -1137,9 +1152,10 @@ in
# 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.
# a session alone would be enough to open it. The
# `operatorsOnly` pairs below are what make the distinction
# real; without them 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`,
@ -1149,7 +1165,9 @@ in
default_policy = "one_factor";
# ⚠️ ORDER MATTERS — authelia takes the FIRST matching rule.
# The metrics rules are listed first so they cannot be
# shadowed by a broader domain rule added later.
# shadowed by a broader domain rule added later. A `deny` with
# no `resources` ends its domain: a rule for that domain
# listed after it is never reached.
rules =
# The collector's client where one is registered, then
# `deny` — see `collectorOnly` above, which is where the
@ -1158,19 +1176,15 @@ in
++ metricsStoreRules
# Also guarded on the domain being set: without it a null
# apex would render a rule matching the string "null".
++ lib.optional (deployCfg.swarm-controller.enable && swarmDomain != null) {
++ lib.optionals (deployCfg.swarm-controller.enable && swarmDomain != null) (operatorsOnly {
domain = swarmDomain;
subject = [ "group:${operatorGroup}" ];
policy = "one_factor";
}
})
# The store's browser UI. Not keyed to a deploy flag: its
# vhost is on the store's host, which need not be this
# one, and without this rule any session passes it.
++ lib.optional (swarmDomain != null) {
# one, and without these rules any session passes it.
++ lib.optionals (swarmDomain != null) (operatorsOnly {
domain = hyperhiveCfg.swarm.bao.ui.domain;
subject = [ "group:${operatorGroup}" ];
policy = "one_factor";
};
});
};
# The cookie domain is the SWARM's domain, NOT authelia's