Commit graph hyperhive/swarm-authelia-bridge/src
Author SHA1 Message Date
atlas
dd1156d45c feat(#3549): refuse agent names the swarm already routes on
An agent name was checked for shape and never for meaning, so a name the
system already answers to could be taken. The list is derived, not invented:
operator/manager/root are broker recipients, and forge/reminder/schedule/
system/todo are message senders.

Senders are in it for a reason that is not cosmetic. The harness switches on
the sender name, so an agent called todo would have its messages read as
todo-wakes and one called system as helper events. That is a parse, not a
display quirk.

⚠️ The check is deliberately its own function rather than part of
validate_username, which runs over every user on every write — including
humans this bridge did not create. Folding it in would mean a store already
containing an operator named 'operator' could never be written again: a rule
about what an agent may be NAMED would have become a rule about what the file
may CONTAIN. A test pins that.

This is the static half only. The dynamic half — a name already taken by
another subject — waits on the heal-vs-refuse question open on #3550.
2026-08-19 23:10:02 +02:00
atlas
6ca4887af4 docs(#3422): the user store is one file, not two
Six places asserted the old design as fact, and none of them mention the
change by name -- the class of doc breakage that is found by asking what
a diff made untrue, not by grepping for a feature:

- swarmctl/README.md and swarm-authelia-bridge/README.md both described
  their own private canonical store. The bridge's "known limitation"
  section described the seam as unsolved; it is what this fixes, so it
  becomes what both writers must uphold instead.
- docs/swarm/{sso,ui,secrets}.md described a rendered artifact.
- The repo CLAUDE.md entry for swarmctl said the same.
- docs/tools/swarmctl-cli.md is regenerated (CI diffs it against the
  clap tree), picking up the removed --store flag.

Operator-facing where it is read: the hand-editing consequence (values
survive a rewrite, comments do not) is stated in sso.md, where an
operator is being told to edit the file, rather than only in a module doc.
2026-08-18 10:34:00 +02:00
atlas
1885022d02 fix(#3422): give bridge-created identities an email too
#3414 fixed the missing-email defect in swarmctl only, so every identity
created by the bridge landed in users.yml with no `email`. A relying
party that asks for the claim does not degrade, it fails -- grafana's
OIDC login is the measured case (#3393).

Two writers of one file disagreeing about a field one of them treats as
required is not a difference worth keeping, and with the file shared the
result also depended on which tool wrote last.

Mutation-checked: removing the fill turns the new test red and nothing
else.
2026-08-18 10:31:48 +02:00
atlas
24f4cd42a9 fix(#3422): the bridge reads and writes users.yml directly
`swarm agent create` failed with "no user store at
swarm-authelia-bridge-users.json but users.yml already holds users" on
any hive that had users. The guard was correct; what was wrong is that
two files both claimed to be canonical for one physical file.

The bridge kept a private users.json and rendered users.yml from it,
while swarmctl kept its own pair against the same users.yml. A writer
whose own JSON was absent could not tell "nothing here yet" from
"someone else's users", so it refused to write at all.

users.yml becomes the store: read before write, through serde_norway
rather than a hand-rolled emitter. Unknown top-level and per-user keys
round-trip through `extra`, or whichever process writes second would
silently delete what the first added. Validation stays on the write path
-- a bare to_string(&store) serialises perfectly and drops the "no
control character ever reaches this file" guarantee silently.

The seed constant goes with the guard: nothing writes a seed now, an
absent file is an empty store, and left as a pub constant it read as if
the seed dance were still load-bearing.
2026-08-18 10:31:48 +02:00
atlas
266e8ac96b fix(#3394): distinguish the caller's bad token from the bridge failing to validate
swarm-authelia-bridge answered one 401 for four causes, including its own
client credentials being rejected by authelia — telling a caller its token
was invalid when the fault was ours. That cost a real diagnostic round-trip,
because the only thing separating the two was a log line inside a container.

Introspection now returns a three-way Verdict, and one classify() maps it to
either admission, a 401 (the caller's credential) or a 503 (ours). Admission
is unchanged: Ok is reachable from exactly one variant.
2026-08-18 00:22:45 +02:00
damocles
405ed85550 fix(swarm-authelia-bridge): lock the load-insert-publish sequence 2026-08-16 22:38:40 +02:00
damocles
fb5d461e52 add swarm-authelia-bridge: the only thing allowed to write swarm-authelia's users database 2026-08-16 22:38:40 +02:00