memoryd: [honcho] and its keys checked at start, and brokerd's state socket
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
//! Every reason an invalid `[honcho]` section, `[[honcho.read]]` or Honcho key is refused with at
|
||||
//! start (M5c spec, sections 6 and 10): each invalid section has one problem and one exact reason.
|
||||
//! Do not edit.
|
||||
|
||||
use memoryd::config::Config;
|
||||
use memoryd::honcho::config::{HonchoConfig, is_name};
|
||||
|
||||
/// The smallest valid section: the six keys, no read workspace, no key.
|
||||
const SECTION: &str = "[honcho]
|
||||
url = \"http://rift.scylla-hammerhead.ts.net:8000\"
|
||||
agent = \"boxmaker0\"
|
||||
owner = \"kyle\"
|
||||
trusted = \"boxmaker0-trusted\"
|
||||
untrusted = \"boxmaker0-untrusted\"
|
||||
fleet = \"fleet\"
|
||||
";
|
||||
|
||||
const URL_REASON: &str =
|
||||
"[honcho] url must be http:// or https://, a host and an optional port, and nothing else";
|
||||
|
||||
/// `SECTION` with `from` (found exactly once) replaced by `to`, then `extra`.
|
||||
fn with(from: &str, to: &str, extra: &str) -> String {
|
||||
assert_eq!(SECTION.matches(from).count(), 1, "{from}");
|
||||
format!("{}{extra}", SECTION.replace(from, to))
|
||||
}
|
||||
|
||||
/// The checked section of `text`, which must parse.
|
||||
fn checked(text: &str) -> Result<Option<HonchoConfig>, String> {
|
||||
let config = Config::parse(text).unwrap_or_else(|e| panic!("{text}\n{e}"));
|
||||
config.honcho_config()
|
||||
}
|
||||
|
||||
fn reason(text: &str) -> String {
|
||||
match checked(text) {
|
||||
Ok(ok) => panic!("accepted:\n{text}\n{ok:?}"),
|
||||
Err(why) => why,
|
||||
}
|
||||
}
|
||||
|
||||
fn name_reason(table: &str, key: &str, name: &str) -> String {
|
||||
format!("{table} {key} {name:?} is not a Honcho name: 1 to 512 of a-z, A-Z, 0-9, _ and -")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn honcho_names_at_the_edges() {
|
||||
let longest = "a".repeat(512);
|
||||
for name in [
|
||||
"a",
|
||||
"Z",
|
||||
"0",
|
||||
"_",
|
||||
"-",
|
||||
"boxmaker0-trusted",
|
||||
"A_b-9",
|
||||
longest.as_str(),
|
||||
] {
|
||||
assert!(is_name(name), "{name}");
|
||||
}
|
||||
let too_long = "a".repeat(513);
|
||||
for name in [
|
||||
"",
|
||||
too_long.as_str(),
|
||||
"boxmaker.0",
|
||||
"box maker",
|
||||
"a/b",
|
||||
"a:b",
|
||||
"kyle@home",
|
||||
"caf\u{e9}",
|
||||
"tab\t",
|
||||
"new\nline",
|
||||
"\u{0660}",
|
||||
] {
|
||||
assert!(!is_name(name), "{name:?}");
|
||||
}
|
||||
// The longest name is accepted in every place a name goes.
|
||||
let text = with("fleet = \"fleet\"", &format!("fleet = \"{longest}\""), "");
|
||||
assert_eq!(checked(&text).unwrap().unwrap().fleet, longest);
|
||||
let text = with("owner = \"kyle\"", &format!("owner = \"{too_long}\""), "");
|
||||
assert_eq!(reason(&text), name_reason("[honcho]", "owner", &too_long));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn urls_that_are_accepted() {
|
||||
for url in [
|
||||
"http://rift.scylla-hammerhead.ts.net:8000",
|
||||
"https://rift.scylla-hammerhead.ts.net",
|
||||
"http://127.0.0.1:1",
|
||||
"http://honcho:65535",
|
||||
"https://Rift-2.Example.org:443",
|
||||
"http://h",
|
||||
] {
|
||||
let text = with(
|
||||
"url = \"http://rift.scylla-hammerhead.ts.net:8000\"",
|
||||
&format!("url = \"{url}\""),
|
||||
"",
|
||||
);
|
||||
assert_eq!(checked(&text).unwrap().unwrap().url, url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn urls_that_are_refused_and_the_reason_never_quotes_them() {
|
||||
for url in [
|
||||
"",
|
||||
"rift:8000",
|
||||
"ftp://rift:8000",
|
||||
"HTTP://rift:8000",
|
||||
"http:/rift",
|
||||
"http://",
|
||||
"https://",
|
||||
"http://:8000",
|
||||
"http://rift:",
|
||||
"http://rift:0",
|
||||
"http://rift:65536",
|
||||
"http://rift:+80",
|
||||
"http://rift:80:90",
|
||||
"http://rift:8000/",
|
||||
"http://rift:8000/v3",
|
||||
"http://rift/",
|
||||
"http://kyle:hunter2@rift:8000",
|
||||
"http://kyle@rift:8000",
|
||||
"http://kyle@rift",
|
||||
"http://rift?x=1",
|
||||
"http://rift#top",
|
||||
"http://ri ft",
|
||||
" http://rift",
|
||||
"http://rift_1",
|
||||
"http://[::1]:8000",
|
||||
] {
|
||||
let text = with(
|
||||
"url = \"http://rift.scylla-hammerhead.ts.net:8000\"",
|
||||
&format!("url = \"{url}\""),
|
||||
"",
|
||||
);
|
||||
assert_eq!(reason(&text), URL_REASON, "{url}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_invalid_section_gives_its_reason() {
|
||||
let read = |agent: &str, workspace: &str| {
|
||||
format!("[[honcho.read]]\nagent = \"{agent}\"\nworkspace = \"{workspace}\"\n")
|
||||
};
|
||||
let cases: Vec<(String, String)> = vec![
|
||||
(
|
||||
with("agent = \"boxmaker0\"", "agent = \"boxmaker.0\"", ""),
|
||||
name_reason("[honcho]", "agent", "boxmaker.0"),
|
||||
),
|
||||
(
|
||||
with("owner = \"kyle\"", "owner = \"\"", ""),
|
||||
name_reason("[honcho]", "owner", ""),
|
||||
),
|
||||
(
|
||||
with(
|
||||
"trusted = \"boxmaker0-trusted\"",
|
||||
"trusted = \"boxmaker0 trusted\"",
|
||||
"",
|
||||
),
|
||||
name_reason("[honcho]", "trusted", "boxmaker0 trusted"),
|
||||
),
|
||||
(
|
||||
with(
|
||||
"untrusted = \"boxmaker0-untrusted\"",
|
||||
"untrusted = \"boxmaker0/untrusted\"",
|
||||
"",
|
||||
),
|
||||
name_reason("[honcho]", "untrusted", "boxmaker0/untrusted"),
|
||||
),
|
||||
(
|
||||
with("fleet = \"fleet\"", "fleet = \"fleet:all\"", ""),
|
||||
name_reason("[honcho]", "fleet", "fleet:all"),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker.1", "boxmaker1-trusted")),
|
||||
name_reason("[[honcho.read]]", "agent", "boxmaker.1"),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker1", "boxmaker1.trusted")),
|
||||
name_reason("[[honcho.read]]", "workspace", "boxmaker1.trusted"),
|
||||
),
|
||||
(
|
||||
with("owner = \"kyle\"", "owner = \"boxmaker0\"", ""),
|
||||
"[honcho] agent and owner are both \"boxmaker0\": the agent's peer must not be the owner's"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
with(
|
||||
"untrusted = \"boxmaker0-untrusted\"",
|
||||
"untrusted = \"boxmaker0-trusted\"",
|
||||
"",
|
||||
),
|
||||
"[honcho] workspace \"boxmaker0-trusted\" is named twice: trusted, untrusted and fleet must be three different workspaces"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
with("fleet = \"fleet\"", "fleet = \"boxmaker0-trusted\"", ""),
|
||||
"[honcho] workspace \"boxmaker0-trusted\" is named twice: trusted, untrusted and fleet must be three different workspaces"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
with("fleet = \"fleet\"", "fleet = \"boxmaker0-untrusted\"", ""),
|
||||
"[honcho] workspace \"boxmaker0-untrusted\" is named twice: trusted, untrusted and fleet must be three different workspaces"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker0", "boxmaker1-trusted")),
|
||||
"[[honcho.read]] agent \"boxmaker0\" is this agent ([honcho] agent)".to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}{}",
|
||||
read("boxmaker1", "boxmaker1-trusted"),
|
||||
read("boxmaker1", "boxmaker1-other")
|
||||
),
|
||||
"[[honcho.read]] agent \"boxmaker1\" is listed twice".to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker1", "boxmaker0-trusted")),
|
||||
"[[honcho.read]] workspace \"boxmaker0-trusted\" is one of this agent's own"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker1", "boxmaker0-untrusted")),
|
||||
"[[honcho.read]] workspace \"boxmaker0-untrusted\" is one of this agent's own"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}{}", read("boxmaker1", "fleet")),
|
||||
"[[honcho.read]] workspace \"fleet\" is one of this agent's own".to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}trust = \"Trusted\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[[honcho.read]] trust \"Trusted\" of agent \"boxmaker1\" is not \"trusted\" or \"untrusted\""
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}trust = \"\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[[honcho.read]] trust \"\" of agent \"boxmaker1\" is not \"trusted\" or \"untrusted\""
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}class = \"top-secret\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[[honcho.read]] class \"top-secret\" of agent \"boxmaker1\" is not \"public\", \"private\" or \"secret\""
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}class = \"Private\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[[honcho.read]] class \"Private\" of agent \"boxmaker1\" is not \"public\", \"private\" or \"secret\""
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}[secrets.honcho_trust]\nenv = \"HONCHO_KEY\"\n"),
|
||||
"[secrets.honcho_trust] is not a Honcho key: memoryd has only honcho_trusted, honcho_untrusted, honcho_fleet and honcho_read_<agent>"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}[secrets.mattermost_token]\nenv = \"MM_TOKEN\"\n"),
|
||||
"[secrets.mattermost_token] is not a Honcho key: memoryd has only honcho_trusted, honcho_untrusted, honcho_fleet and honcho_read_<agent>"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}[secrets.honcho_read_boxmaker2]\nenv = \"HONCHO_KEY\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[secrets.honcho_read_boxmaker2] is the key of agent \"boxmaker2\", which is not in [[honcho.read]]"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}[secrets.honcho_read_boxmaker0]\nenv = \"HONCHO_KEY\"\n"),
|
||||
"[secrets.honcho_read_boxmaker0] is the key of agent \"boxmaker0\", which is not in [[honcho.read]]"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}[secrets.honcho_trusted]\n"),
|
||||
"[secrets.honcho_trusted] needs exactly one of credential, env and file".to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}[secrets.honcho_fleet]\nenv = \"HONCHO_KEY\"\nfile = \"/etc/honcho.key\"\n"
|
||||
),
|
||||
"[secrets.honcho_fleet] needs exactly one of credential, env and file".to_string(),
|
||||
),
|
||||
(
|
||||
format!("{SECTION}[secrets.honcho_untrusted]\nenv = \"honcho_key\"\n"),
|
||||
"[secrets.honcho_untrusted] env \"honcho_key\" is not a variable name (A-Z, 0-9, _)"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"{SECTION}{}[secrets.honcho_read_boxmaker1]\nfile = \"keys/boxmaker1\"\n",
|
||||
read("boxmaker1", "boxmaker1-trusted")
|
||||
),
|
||||
"[secrets.honcho_read_boxmaker1] file \"keys/boxmaker1\" must be an absolute path"
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"[secrets.honcho_trusted]\nenv = \"HONCHO_KEY\"\n".to_string(),
|
||||
"[secrets.honcho_trusted] is set, but there is no [honcho] section".to_string(),
|
||||
),
|
||||
];
|
||||
for (text, want) in &cases {
|
||||
assert_eq!(&reason(text), want, "\n{text}");
|
||||
}
|
||||
assert_eq!(cases.len(), 29);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_first_problem_is_the_one_reported() {
|
||||
// A bad url and an owner that is the agent: the url is checked first.
|
||||
let text = with(
|
||||
"url = \"http://rift.scylla-hammerhead.ts.net:8000\"",
|
||||
"url = \"rift\"",
|
||||
"",
|
||||
)
|
||||
.replace("owner = \"kyle\"", "owner = \"boxmaker0\"");
|
||||
assert_eq!(reason(&text), URL_REASON);
|
||||
// A read agent that is this agent, with a bad trust: the agent is checked first.
|
||||
let text = format!(
|
||||
"{SECTION}[[honcho.read]]\nagent = \"boxmaker0\"\nworkspace = \"w\"\ntrust = \"no\"\n"
|
||||
);
|
||||
assert_eq!(
|
||||
reason(&text),
|
||||
"[[honcho.read]] agent \"boxmaker0\" is this agent ([honcho] agent)"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user