Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: stop minting the per-hive matrix sender account and token nothing reads

This commit is contained in:
damocles 2026-11-12 15:38:34 +01:00
commit c66832febf
3 changed files with 40 additions and 556 deletions

View file

@ -47,8 +47,7 @@ pub fn agent_token_path(agent: &str) -> Result<String, Error> {
/// basic-auth header that token travels in.
///
/// The `hive-` prefix keeps the account reading as a hive's rather than an
/// agent's, as [`crate::matrix::hive_localpart`] does on the homeserver.
/// `swarm-controller` refuses a new agent named with this prefix, and tells an
/// agent's. `swarm-controller` refuses a new agent named with this prefix, and tells an
/// older one apart from the hive's account by the account's email.
#[must_use]
pub fn hive_login(hive: &str) -> String {

View file

@ -70,44 +70,6 @@ impl std::fmt::Debug for Recovery {
}
}
/// The localpart `hive` acts as on the homeserver: the account
/// `swarm-controller` creates for it with the swarm's appservice token, and
/// the one hive-c0re's sender token belongs to.
///
/// **Derived from the hive's name, which is what makes it one account per
/// hive.** It used to be the bare constant `hive`: one swarm runs one
/// homeserver, so every hive on it logged in as the same `@hive:` and the
/// homeserver could not tell them apart — no attribution, and no way to revoke
/// one hive without revoking all of them.
///
/// The prefix is kept so the account still reads as a hive's rather than an
/// agent's. Both `swarm-controller` and hive-c0re derive it here, so the two
/// cannot disagree on which account a hive is.
///
/// Infallible on purpose: a hive name is `[a-z0-9-]` (`hive_types::Ident`) and
/// the appservice namespace regex admits that charset, so there is no
/// rejection arm to write. [`sender_token_path`] below does the checking, and
/// it is the one that interpolates into a store path.
#[must_use]
pub fn hive_localpart(hive: &str) -> String {
format!("hive-{hive}")
}
/// The path holding `hive`'s matrix sender account's homeserver access token.
///
/// Keyed per **hive**, like [`crate::queue::hive_client_path`]. Under
/// [`Kind::Hive`] the grant that reaches it is the hive's **own** stanza,
/// `secret/data/swarm/hives/<this hive>/*`, which interpolates the name: hive
/// `alpha` reads its own token and gets a 403 on `beta`'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 sender_token_path(hive: &str) -> Result<String, Error> {
let prefix = principal_prefix(Kind::Hive, hive)?;
Ok(format!("{prefix}/matrix/sender-token"))
}
/// The name segment of the controller's own subtree, and the cert-auth role it
/// logs in under (`swarm-controller`'s `store::CERT_ROLE`).
pub(crate) const CONTROLLER: &str = "swarm-controller";
@ -254,48 +216,6 @@ mod tests {
assert!(recovery_path("a-b_C9", "d-e_F0").is_ok());
}
#[test]
fn the_sender_token_lands_under_the_hive_prefix_the_grant_covers() {
// Spelled out rather than rebuilt from the same pieces the code uses,
// for the reason above — and with a second job here: the hive's own
// policy grants read on `secret/data/swarm/hives/<this hive>/*`, so
// this exact string is what makes the path reachable at all.
assert_eq!(
sender_token_path("pr1ma").expect("a plain name is legal"),
"swarm/hives/pr1ma/matrix/sender-token"
);
}
#[test]
fn one_hives_sender_token_is_not_another_hives() {
// The property the per-hive path exists for: two hives never name the
// same object, so the `hives/<name>/*` grant that reaches one cannot
// reach the other.
let a = sender_token_path("alpha").expect("legal");
let b = sender_token_path("beta").expect("legal");
assert_ne!(a, b);
}
#[test]
fn a_traversal_in_the_hive_name_is_refused_by_the_sender_path_too() {
let e = sender_token_path("../beta").expect_err("a traversal is not legal");
assert!(matches!(e, Error::PathSegment { kind: "hive", .. }), "{e}");
}
#[test]
fn the_localpart_is_derived_from_the_hive_name() {
// The literal is the point: an existing hive's account on the
// homeserver carries this localpart, so a change here strands it.
assert_eq!(hive_localpart("pr1ma"), "hive-pr1ma");
assert_ne!(
hive_localpart("pr1ma"),
hive_localpart("secunda"),
"two hives must not land on one account"
);
// And it is never the bare `hive` that used to be shared swarm-wide.
assert_ne!(hive_localpart("pr1ma"), "hive");
}
#[test]
fn the_swarm_appservice_token_lands_under_the_controller() {
assert_eq!(

View file

@ -377,15 +377,15 @@ mod tests {
);
}
/// 🩸 The per-hive matrix sender-token scoping, asserted on the rendered
/// 🩸 The per-hive forge-token scoping, asserted on the rendered
/// document rather than on the path function alone: it is the policy text
/// that decides what a hive may fetch, so "the path is per-hive" only
/// means something if the stanza that reaches it is too.
#[test]
fn a_hive_reaches_its_own_matrix_sender_token_and_no_other_hives() {
fn a_hive_reaches_its_own_forge_token_and_no_other_hives() {
let alpha = effective_hive("alpha");
let own = crate::matrix::sender_token_path("alpha").expect("legal");
let other = crate::matrix::sender_token_path("beta").expect("legal");
let own = crate::forge::hive_token_path("alpha").expect("legal");
let other = crate::forge::hive_token_path("beta").expect("legal");
// Reachable: the hive's own stanza is the prefix of its own path.
assert!(