swarm-controller: refuse new agents named hive-<anything>
`hive-<hive>` is each hive's read-only forge account, and an agent's forge account takes the agent's name, so an agent called `hive-<x>` would claim hive `<x>`'s account. `hive_types::reserved_agent_prefix_violation` names the rule, and `broken_name_rules` applies it, so both creation paths that go through it (`POST /api/agents` and placing an undeclared agent on a hive) refuse such a name for a new agent. An agent already in the roster keeps its name with a warning, as for every other naming rule. The email check between hive and agent accounts stays as the second guard, for agents created before this rule. Refs #4851
This commit is contained in:
parent
21e3f626f4
commit
4e7142ae53
5 changed files with 78 additions and 13 deletions
|
|
@ -58,12 +58,13 @@ creates what's missing and re-mints a token that's missing or no longer
|
||||||
matches the forge. A pass whose store or forge reads fail queues nothing, so
|
matches the forge. A pass whose store or forge reads fail queues nothing, so
|
||||||
an outage never rotates every hive's token.
|
an outage never rotates every hive's token.
|
||||||
|
|
||||||
The account name isn't reserved, so an agent may carry the name `hive-<x>` too.
|
swarm-controller refuses to create an agent whose name starts with `hive-`:
|
||||||
swarm-controller creates the hive's account with the email
|
these accounts own that prefix. An agent that already had such a
|
||||||
`hive-<hive>@hives.hyperhive.local` and refuses an account by that name with
|
name keeps it, so the accounts are also told apart by email. swarm-controller
|
||||||
any other email: it never adds an agent's account to the team or mints a
|
creates the hive's account with the email `hive-<hive>@hives.hyperhive.local`
|
||||||
token for it. An agent's own creation refuses the hive's account the same
|
and refuses an account by that name with any other email: it never adds an
|
||||||
way.
|
agent's account to the team or mints a token for it. An agent's own forge
|
||||||
|
account setup refuses the hive's account the same way.
|
||||||
|
|
||||||
hive-c0re reads the token with `forge::reader_token()`, under the hive's own
|
hive-c0re reads the token with `forge::reader_token()`, under the hive's own
|
||||||
store certificate, every time it reads the forge. Where the store holds none,
|
store certificate, every time it reads the forge. Where the store holds none,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,24 @@ pub fn forge_username_violation(name: &str) -> Option<&'static str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The prefix no new agent's name may start with. `hive-<hive>` is each hive's
|
||||||
|
/// own forge account (`swarm_secret_client::forge::hive_login`), and every
|
||||||
|
/// agent's forge account takes the agent's name, so an agent called
|
||||||
|
/// `hive-<x>` would claim hive `<x>`'s account.
|
||||||
|
pub const RESERVED_AGENT_PREFIX: &str = "hive-";
|
||||||
|
|
||||||
|
/// The reserved-prefix rule `name` breaks as a new agent's name, or `None`.
|
||||||
|
///
|
||||||
|
/// Creation sites only, for the reason given at [`is_reserved_name`]. Agent
|
||||||
|
/// names only: hive names are kept off `hive` by
|
||||||
|
/// `nix/reserved-hive-fragments.nix`, and the other [`Ident`]s never become a
|
||||||
|
/// forge user.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reserved_agent_prefix_violation(name: &str) -> Option<&'static str> {
|
||||||
|
name.starts_with(RESERVED_AGENT_PREFIX)
|
||||||
|
.then_some("must not start with 'hive-': that prefix is reserved for hive forge accounts")
|
||||||
|
}
|
||||||
|
|
||||||
/// A validated hive identifier: 1-63 chars of `[a-z0-9-]`.
|
/// A validated hive identifier: 1-63 chars of `[a-z0-9-]`.
|
||||||
///
|
///
|
||||||
/// The single ident type for agent names, forge labels, and matrix / github
|
/// The single ident type for agent names, forge labels, and matrix / github
|
||||||
|
|
@ -187,7 +205,7 @@ impl<'de> serde::Deserialize<'de> for Ident {
|
||||||
mod ident_tests {
|
mod ident_tests {
|
||||||
use super::{
|
use super::{
|
||||||
FORGE_USERNAME_MAX_LEN, Ident, forge_username_violation, is_reserved_name,
|
FORGE_USERNAME_MAX_LEN, Ident, forge_username_violation, is_reserved_name,
|
||||||
parse_reserved_names,
|
parse_reserved_names, reserved_agent_prefix_violation,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -302,6 +320,20 @@ mod ident_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_hive_prefix_is_refused_for_an_agent() {
|
||||||
|
for bad in ["hive-x", "hive-alpha", "hive-"] {
|
||||||
|
assert!(
|
||||||
|
reserved_agent_prefix_violation(bad).is_some_and(|r| r.contains("hive forge")),
|
||||||
|
"{bad:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The accept arm: `hive` elsewhere in the name, or without the dash.
|
||||||
|
for ok in ["hivemind", "xhive-y", "hive", "my-hive", "hives-x"] {
|
||||||
|
assert_eq!(reserved_agent_prefix_violation(ok), None, "{ok:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn round_trips_and_serde_validates() {
|
fn round_trips_and_serde_validates() {
|
||||||
let id = Ident::parse("damocles").unwrap();
|
let id = Ident::parse("damocles").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@
|
||||||
//! token the forge still lists with the same last eight characters and scopes
|
//! token the forge still lists with the same last eight characters and scopes
|
||||||
//! is kept, and a pass whose reads fail queues nothing.
|
//! is kept, and a pass whose reads fail queues nothing.
|
||||||
//!
|
//!
|
||||||
//! ⚠️ The account name is not reserved: an agent may be called `hive-<x>`.
|
//! A new agent may not be called `hive-<x>`
|
||||||
//! Each side refuses an account carrying the other's email, so neither ever
|
//! (`hive_types::reserved_agent_prefix_violation`, checked at agent creation),
|
||||||
//! locks down, joins the team with, or mints a token for the other's account.
|
//! which is the primary guard against an agent claiming a hive's account. An
|
||||||
|
//! agent created before that rule can still hold such a name, so each side
|
||||||
|
//! also refuses an account carrying the other's email: neither ever locks
|
||||||
|
//! down, joins the team with, or mints a token for the other's account.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1882,6 +1882,9 @@ fn broken_name_rules(
|
||||||
"agent name {agent:?} {rule}: the forge refuses it as a username"
|
"agent name {agent:?} {rule}: the forge refuses it as a username"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(rule) = hive_types::reserved_agent_prefix_violation(agent) {
|
||||||
|
broken.push(format!("agent name {agent:?} {rule}"));
|
||||||
|
}
|
||||||
broken
|
broken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3892,6 +3895,32 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `hive-` prefix belongs to hives' forge accounts, so a new agent
|
||||||
|
/// may not take it. `hivemind` and `xhive-y` are the control.
|
||||||
|
#[test]
|
||||||
|
fn a_new_agent_with_the_hive_prefix_is_refused() {
|
||||||
|
let raw = nix_reserved();
|
||||||
|
let mut warnings = Vec::new();
|
||||||
|
let broken = super::broken_name_rules("hive-x", Some(&raw), &mut warnings);
|
||||||
|
let err =
|
||||||
|
super::name_verdict("hive-x", &broken, Ok(false), &mut warnings).expect_err("hive-x");
|
||||||
|
assert!(err.contains("reserved for hive forge accounts"), "{err}");
|
||||||
|
for ok in ["hivemind", "xhive-y"] {
|
||||||
|
let broken = super::broken_name_rules(ok, Some(&raw), &mut warnings);
|
||||||
|
assert!(broken.is_empty(), "{ok:?}: {broken:?}");
|
||||||
|
}
|
||||||
|
assert!(warnings.is_empty(), "{warnings:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The prefix the rule reserves is the one hive accounts are named with.
|
||||||
|
#[test]
|
||||||
|
fn the_reserved_prefix_is_the_hive_account_prefix() {
|
||||||
|
assert!(
|
||||||
|
swarm_secret_client::forge::hive_login("alpha")
|
||||||
|
.starts_with(hive_types::RESERVED_AGENT_PREFIX)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-creating an agent under its own name must keep working: a name
|
/// Re-creating an agent under its own name must keep working: a name
|
||||||
/// already in the roster is warned about, never refused.
|
/// already in the roster is warned about, never refused.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ pub fn agent_token_path(agent: &str) -> Result<String, Error> {
|
||||||
/// basic-auth header that token travels in.
|
/// basic-auth header that token travels in.
|
||||||
///
|
///
|
||||||
/// The `hive-` prefix keeps the account reading as a hive's rather than an
|
/// 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. It
|
/// agent's, as [`crate::matrix::hive_localpart`] does on the homeserver.
|
||||||
/// does not make the name unique: an agent may be called `hive-<x>` too, and
|
/// `swarm-controller` refuses a new agent named with this prefix, and tells an
|
||||||
/// `swarm-controller` tells the two apart by the account's email.
|
/// older one apart from the hive's account by the account's email.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn hive_login(hive: &str) -> String {
|
pub fn hive_login(hive: &str) -> String {
|
||||||
format!("hive-{hive}")
|
format!("hive-{hive}")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue