Watch
0
0
Fork
You've already forked hyperhive
0

add one shared templated hive policy keyed on the cert alias name

This commit is contained in:
atlas 2026-10-26 08:57:24 +01:00 • committed by mara
commit 03796f1964
4 changed files with 232 additions and 12 deletions

View file

@ -151,15 +151,49 @@ fn list_stanza(path: &str) -> String {
/// stanza could grant itself anything.
pub fn render(hive: &str) -> Result<String, Error> {
checked_segment("hive", hive)?;
Ok(hive_document(hive))
}
/// The text of [`render`] with `own` as the hive segment: a checked hive name,
/// or the template [`render_shared_hives`] puts there.
fn hive_document(own: &str) -> String {
let agents = read_stanza(&format!(
"{MOUNT}/data/{ROOT}/{}/*",
<&str>::from(Kind::Agent)
));
let own = read_stanza(&format!(
"{MOUNT}/data/{ROOT}/{}/{hive}/*",
"{MOUNT}/data/{ROOT}/{}/{own}/*",
<&str>::from(Kind::Hive)
));
Ok(format!("{agents}{own}"))
format!("{agents}{own}")
}
/// The one policy every hive's cert-auth role attaches next to its own.
///
/// Inside [`HIVE_PREFIX`] so that the controller may write it. It is the object
/// name of a hive called `hives`, and `nix/reserved-hive-fragments.nix` forbids
/// the substring `hive` in every hive name, so no hive spells it.
pub const SHARED_HIVE_POLICY: &str = "hive-hives";
/// Render the policy [`SHARED_HIVE_POLICY`] holds: the text of [`render`], with
/// the hive's name taken from the alias of its certificate login on the cert
/// auth mount `mount_accessor`.
///
/// A hive's certificate carries its bare name as the common name, and that is
/// the alias name, so the own-path stanza reads `swarm/hives/<hive>/` for
/// whichever hive presents the token. A login whose alias is not a hive's name
/// reads a path where nothing is stored. The `agents/` and `hives/` segments
/// are fixed text, never taken from the identity.
///
/// # Errors
/// [`Error::PathSegment`] when `mount_accessor` holds anything but
/// `[A-Za-z0-9_-]`: it is interpolated into the policy text, where it could
/// close the stanza.
pub fn render_shared_hives(mount_accessor: &str) -> Result<String, Error> {
checked_segment("mount accessor", mount_accessor)?;
Ok(hive_document(&format!(
"{{{{identity.entity.aliases.{mount_accessor}.name}}}}"
)))
}
/// Render `agent`'s policy document: read on that one agent's credentials, list
@ -286,6 +320,68 @@ mod tests {
);
}
#[test]
fn the_shared_hive_document_is_each_hives_own_with_the_alias_name_for_the_hive() {
let shared = render_shared_hives("auth_cert_1a2b3c4d").expect("legal accessor");
assert_eq!(
shared,
"path \"secret/data/swarm/agents/*\" {\n capabilities = [\"read\"]\n}\n\
path \"secret/data/swarm/hives/{{identity.entity.aliases.auth_cert_1a2b3c4d.name}}/*\" {\n capabilities = [\"read\"]\n}\n"
);
// Same text as every hive's own document once the template is filled
// in, for hives that differ in length, case and punctuation.
for hive in ["pr1ma", "a", "Mixed_Case-1", "x".repeat(63).as_str()] {
assert_eq!(
shared.replace("{{identity.entity.aliases.auth_cert_1a2b3c4d.name}}", hive),
render(hive).expect("legal"),
"{hive}"
);
}
}
#[test]
fn the_shared_hive_document_names_no_hive_and_grants_no_wider_than_one() {
let shared = render_shared_hives("auth_cert_1a2b3c4d").expect("legal accessor");
assert!(!shared.contains("pr1ma"));
assert!(!shared.contains("hives/*"), "{shared}");
assert!(!shared.contains("create"));
assert!(!shared.contains("update"));
assert!(!shared.contains("delete"));
assert!(!shared.contains("list"));
// The control: the walk finds the stanzas that are there.
assert_eq!(stanza_paths(&shared).len(), 2, "{shared}");
assert!(
stanza_paths(&shared)
.iter()
.any(|g| g.starts_with("swarm/hives/{{"))
);
}
#[test]
fn an_accessor_that_could_close_the_stanza_is_refused() {
for bad in ["", "a.b", "a}}b", "a\"b", "a/b", "a b"] {
assert!(
matches!(render_shared_hives(bad), Err(Error::PathSegment { .. })),
"{bad:?}"
);
}
}
#[test]
fn the_shared_hive_policy_name_is_writable_and_no_agents() {
assert!(SHARED_HIVE_POLICY.starts_with(HIVE_PREFIX));
assert!(
agents_from_role_names(&[SHARED_HIVE_POLICY.to_owned()]).is_empty(),
"it must not be read back as an agent"
);
assert_ne!(SHARED_HIVE_POLICY, SHARED_AGENT_POLICY);
// The only hive name that would render it carries `hive`.
assert_eq!(
hive_object_name("hives").expect("legal as a segment"),
SHARED_HIVE_POLICY
);
}
#[test]
fn a_hive_reaches_no_swarm_services_secret() {
// A swarm service's OIDC client secret is read under a certificate that