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.
This commit is contained in:
parent
1885022d02
commit
6ca4887af4
10 changed files with 114 additions and 97 deletions
|
|
@ -1,37 +1,20 @@
|
|||
//! The canonical user store this bridge owns, and the authelia users
|
||||
//! database rendered from it.
|
||||
//!
|
||||
//! Deliberately its own copy, not shared code with `swarmctl::users` even
|
||||
//! though the YAML shape is identical (same "factor out later if
|
||||
//! duplication actually bites" reasoning as `swarm-controller::forge`
|
||||
//! mirroring `hive-c0re::forge` — see the agent-identity-at-swarm-level
|
||||
//! design thread). One real difference from `swarmctl`'s copy: this
|
||||
//! store lives **wherever this
|
||||
//! bridge runs** (inside the `swarm-authelia` container, alongside
|
||||
//! `authelia-swarm`'s own state), not under `swarm-controller`'s state
|
||||
//! dir — the two are not guaranteed to be the same host once
|
||||
//! `swarm-authelia` and `swarm-controller` split across hosts, and this
|
||||
//! bridge only ever runs where `swarm-authelia` does.
|
||||
//!
|
||||
//! # `users.yml` is the store, not a rendering of one
|
||||
//! Authelia's users database — **the** store, not a rendering of one.
|
||||
//!
|
||||
//! There used to be a private `users.json` here, canonical, with `users.yml`
|
||||
//! rendered from it — and `swarmctl` had its own pair against the *same*
|
||||
//! physical `users.yml`. Two canonical stores for one file is a seam, and it
|
||||
//! bit: a bridge whose own JSON was absent refused to write at all, because
|
||||
//! it could not tell "nothing here yet" from "someone else's users".
|
||||
//! rendered from it, while `swarmctl` kept its own pair against the *same*
|
||||
//! physical file. Two canonical stores for one file is a seam, and it bit: a
|
||||
//! writer whose own JSON was absent refused to write at all, unable to tell
|
||||
//! "nothing here yet" from "someone else's users". The JSON bought nothing —
|
||||
//! it was read back on every load, so the round-trip it seemed to avoid was
|
||||
//! already being paid.
|
||||
//!
|
||||
//! The JSON bought nothing. It was read back on every load, so the
|
||||
//! round-trip it seemed to avoid was already being paid — the two files
|
||||
//! differed only in *format*. One file removes the class: there is no second
|
||||
//! store to disagree with, and the overwrite guard that policed them has
|
||||
//! nothing left to do.
|
||||
//! ⚠️ The file is **round-tripped**, so comments and hand-formatting do not
|
||||
//! survive a write; values and unmodelled keys do (see `extra`).
|
||||
//!
|
||||
//! ⚠️ Consequence, deliberate: this file is now **round-tripped**, so
|
||||
//! comments and hand-formatting in it do not survive a write. An operator
|
||||
//! editing it directly gets their *values* kept and their *comments* dropped.
|
||||
//! Unknown keys are preserved (see `extra` below) so a field this binary does
|
||||
//! not know about is not deleted by it.
|
||||
//! Deliberately its own copy of the shape rather than code shared with
|
||||
//! `swarmctl::users` — same "factor out if the duplication actually bites"
|
||||
//! reasoning as `swarm-controller::forge` mirroring `hive-c0re::forge`. What
|
||||
//! both copies must uphold is in `../README.md`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{self, File, Permissions};
|
||||
|
|
@ -188,13 +171,14 @@ const SYNTHETIC_EMAIL_DOMAIN: &str = "hyperhive.local";
|
|||
|
||||
/// Give every user an address before the file is written.
|
||||
///
|
||||
/// 🩸 **This bridge did not do it, and `swarmctl` did.** #3414 fixed the
|
||||
/// missing-email defect in `swarmctl` only, so every identity created *here*
|
||||
/// landed in `users.yml` with no `email` — and 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
|
||||
/// required field is not a difference worth keeping, so the rule now lives
|
||||
/// on both write paths.
|
||||
/// 🩸 **This bridge did not do it, and `swarmctl` did.** The fix for the
|
||||
/// missing-email defect landed in `swarmctl` only, so every identity created
|
||||
/// *here* landed in `users.yml` with no `email` — and a relying party that
|
||||
/// asks for the claim does not degrade, it fails (grafana's OIDC login is
|
||||
/// the measured case: it falls through to an endpoint authelia does not
|
||||
/// implement and dies with an internal error). Two writers of one file
|
||||
/// disagreeing about a required field is not a difference worth keeping, so
|
||||
/// the rule now lives on both write paths.
|
||||
fn fill_missing_emails(store: &mut UserStore) {
|
||||
for (name, user) in &mut store.users {
|
||||
if user.email.is_none() {
|
||||
|
|
@ -342,11 +326,11 @@ some_future_top_level_key: 7
|
|||
|
||||
/// `write_atomic` round-trips through a real temp dir — the property
|
||||
/// under test is the rename-into-place, not just the render.
|
||||
/// 🩸 The asymmetry this fixes: #3414 gave `swarmctl`-created humans a
|
||||
/// synthetic address and left bridge-created **agents** with none, so
|
||||
/// two writers of one file disagreed about a field a relying party
|
||||
/// treats as required — grafana's OIDC login fails outright without it
|
||||
/// (#3393) rather than degrading.
|
||||
/// 🩸 The asymmetry this fixes: the earlier missing-email fix gave
|
||||
/// `swarmctl`-created humans a synthetic address and left bridge-created
|
||||
/// **agents** with none, so two writers of one file disagreed about a
|
||||
/// field a relying party treats as required — grafana's OIDC login fails
|
||||
/// outright without it rather than degrading.
|
||||
///
|
||||
/// Asserted through the real write path, because that is where the rule
|
||||
/// lives: `handle` inserts a user with `email: None` and nothing between
|
||||
|
|
|
|||
Loading…
Reference in a new issue