//! 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, 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_" .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_" .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)" ); }