swarm: put the matrix registration token where the reader is granted
`swarm-bao-matrix-token` reads `secret/swarm/matrix/registration-token` and is refused with `Code: 403 — permission denied`, measured on this host at 16:17:49Z after a successful cert login. path.rs makes every swarm path `swarm/<kind>/<name>/…` where Kind is a closed set of four: agents, hives, services, controller. `matrix` sits where a kind belongs, so policy.rs's read document — which emits exactly `swarm/agents/*` and `swarm/hives/<hive>/*` — cannot cover it. The module's own doc predicted this: "a misspelled kind is a 403 at provision time rather than anything a compiler sees". Moves the token to `swarm/hives/<hive>/matrix/registration-token`, built through principal_prefix(Kind::Hive, …) like its per-hive sibling queue::agent_client_path. The policy is untouched: render already grants that prefix. mara chose this over widening the namespace. The nix reader interpolates hyperhiveCfg.hiveName, with the no-fallback reasoning glue-bao-tls.nix already gives at its own use of it. path.rs's MOUNT doc justified itself by citing the old literal, which this commit deletes; rewritten to cite the nix reader instead. Scope: this makes the read reachable, not the value present. Nothing writes that path yet, and a 403 says nothing about presence — the two are separate findings and only the first is fixed here. No migration: nothing ever wrote the old path and no read ever succeeded. Gate: cargo fmt 0, cargo test -p swarm-secret-client 0 — 32 passed against a 29-passed baseline with the change stashed, so the three new tests are accounted for rather than assumed. Refs #4308
This commit is contained in:
parent
2a02c76ad5
commit
14305255f0
4 changed files with 70 additions and 4 deletions
|
|
@ -26,6 +26,20 @@ pub fn account_path(agent: &str, account: &str) -> Result<String, Error> {
|
|||
Ok(format!("{prefix}/matrix/{account}"))
|
||||
}
|
||||
|
||||
/// The path holding `hive`'s matrix registration token.
|
||||
///
|
||||
/// Keyed per **hive**, not per agent, like [`crate::queue::agent_client_path`]
|
||||
/// and unlike [`account_path`] above: one homeserver admits one hive's
|
||||
/// accounts, so the token that creates them is the hive's.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`Error::PathSegment`] when `hive` contains anything but `[A-Za-z0-9_-]`,
|
||||
/// which is what keeps one hive's name from addressing another hive's secret.
|
||||
pub fn registration_token_path(hive: &str) -> Result<String, Error> {
|
||||
let prefix = principal_prefix(Kind::Hive, hive)?;
|
||||
Ok(format!("{prefix}/matrix/registration-token"))
|
||||
}
|
||||
|
||||
/// What an account's path holds: the token, plus the homeserver it belongs to.
|
||||
///
|
||||
/// The homeserver rides with the token rather than on the queue notice that
|
||||
|
|
@ -65,6 +79,32 @@ mod tests {
|
|||
assert_eq!(p, "swarm/agents/atlas/matrix/ops-relay");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_registration_token_lands_under_the_hive_prefix_the_grant_covers() {
|
||||
// Spelled out for the same reason as above, and with a second job here:
|
||||
// the read policy grants `secret/data/swarm/hives/<hive>/*`, so this
|
||||
// string is what makes the path reachable at all.
|
||||
assert_eq!(
|
||||
registration_token_path("pr1ma").expect("a plain name is legal"),
|
||||
"swarm/hives/pr1ma/matrix/registration-token"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_registration_token_is_not_a_top_level_namespace() {
|
||||
// The path it used to hold. `Kind` is a closed set and `matrix` is not
|
||||
// one of its members, so a path with `matrix` as the second segment is
|
||||
// outside every grant — which is how it came to 403 on every read.
|
||||
let p = registration_token_path("pr1ma").expect("legal");
|
||||
assert!(!p.starts_with("swarm/matrix/"), "{p}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_traversal_in_the_hive_name_is_refused() {
|
||||
let e = registration_token_path("../beta").expect_err("a traversal is not");
|
||||
assert!(matches!(e, Error::PathSegment { kind: "hive", .. }), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_segment_cannot_escape_its_own_directory() {
|
||||
// Each of these is a *different* way to address another agent's tree,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ use crate::Error;
|
|||
|
||||
/// The KV v2 mount every swarm secret lives under.
|
||||
///
|
||||
/// A literal because the store's existing reader already hardcodes the same
|
||||
/// one (`secret/swarm/matrix/registration-token`); an option nobody sets would
|
||||
/// be two ways to say one thing.
|
||||
/// A literal because the nix-side reader spells the same one in
|
||||
/// `glue-matrix-bao-token.nix`; an option nobody sets would be two ways to say
|
||||
/// one thing.
|
||||
pub const MOUNT: &str = "secret";
|
||||
|
||||
/// The root under [`MOUNT`] that every swarm secret lives beneath.
|
||||
|
|
|
|||
Loading…
Reference in a new issue