//! Where a credential lives, for both ends of the store. //! //! The controller writes and a hive reads, and neither is senior to the other, //! so the path they must agree on is built here rather than formatted at each //! call site. 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. pub const MOUNT: &str = "secret"; /// The prefix under [`MOUNT`] owned by per-agent credentials. pub const AGENT_PREFIX: &str = "swarm/agents"; /// A path segment that cannot change the path's shape. /// /// The charset is deliberately narrower than what the store accepts: a `/` /// turns one agent's segment into another agent's directory, and `..` walks /// out of the prefix entirely. Both are names this crate receives from /// elsewhere — an agent name from the topology, an account name from an /// agent's own config — so neither is trusted to be well-formed here. /// /// Public because the same name is also used to build a path **on disk**, and /// that guard must accept exactly what this one does. Two copies of a charset /// are two charsets: they agree until one is edited, and the day they diverge /// is the day a name is legal in the store and not on the filesystem, or the /// reverse. /// /// # Errors /// [`Error::PathSegment`] when `value` is empty or holds anything outside /// `[A-Za-z0-9_-]`. pub fn checked_segment(kind: &'static str, value: &str) -> Result<(), Error> { if value.is_empty() { return Err(Error::PathSegment { kind, value: value.to_owned(), }); } if !value .bytes() .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_') { return Err(Error::PathSegment { kind, value: value.to_owned(), }); } Ok(()) } /// The path holding `agent`'s token for the external matrix account `account`. /// /// # Errors /// [`Error::PathSegment`] when either name contains anything but /// `[A-Za-z0-9_-]`, which is what keeps one agent's name from addressing /// another agent's secret. pub fn matrix_account(agent: &str, account: &str) -> Result { checked_segment("agent", agent)?; checked_segment("account", account)?; Ok(format!("{AGENT_PREFIX}/{agent}/matrix/{account}")) } #[cfg(test)] mod tests { use super::*; #[test] fn a_well_formed_pair_lands_under_the_agent_prefix() { let p = matrix_account("atlas", "ops-relay").expect("both segments are legal"); assert_eq!(p, "swarm/agents/atlas/matrix/ops-relay"); assert!(p.starts_with(AGENT_PREFIX)); } #[test] fn a_segment_cannot_escape_its_own_directory() { // Each of these is a *different* way to address another agent's tree, // and the last two are the ones a charset check catches but a // `contains("..")` check does not. for bad in [ "../argus", "atlas/../argus", "atlas/matrix", "a b", "a.b", "", ] { assert!( matrix_account(bad, "ops-relay").is_err(), "agent segment {bad:?} must be refused" ); assert!( matrix_account("atlas", bad).is_err(), "account segment {bad:?} must be refused" ); } } #[test] fn the_legal_charset_is_actually_reachable() { // The control for the test above: if `checked_segment` rejected // everything, the escape cases would pass for the wrong reason. assert!(matrix_account("a-b_C9", "d-e_F0").is_ok()); } }