//! `[honcho]` in `memoryd.toml`, its `[[honcho.read]]` list and its keys, checked at start (M5c //! spec, section 10), and `[sockets] state`, `brokerd`'s session-state socket: what a valid //! section gives, and how `load` refuses an invalid one. The reasons are in `honcho_reasons.rs`. //! Do not edit. #[path = "support/tmp.rs"] mod tmp; use std::collections::BTreeMap; use std::path::PathBuf; use bxcore::DataClass; use bxcore::secrets::SecretSource; use memoryd::config::{Config, ConfigError}; use memoryd::honcho::config::{ FLEET_KEY, HonchoConfig, MAX_NAME, READ_KEY, RUNBOOK, ReadWorkspace, TRUSTED_KEY, Trust, UNTRUSTED_KEY, read_key, }; use tmp::TempDir; /// 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\" "; /// Two read workspaces: the first with the defaults, the second with both written out. const READS: &str = "[[honcho.read]] agent = \"boxmaker1\" workspace = \"boxmaker1-trusted\" [[honcho.read]] agent = \"boxmaker2\" workspace = \"boxmaker2-trusted\" trust = \"trusted\" class = \"secret\" "; /// `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() } #[test] fn without_a_honcho_section_it_is_none() { for text in [ "", "[paths]\nhome = \"/srv/bx\"\n", "[memory]\nreview = true\n[sockets]\nloop = \"/r/l.sock\"\n", ] { assert_eq!(checked(text), Ok(None), "{text}"); } } #[test] fn the_smallest_section_is_checked_as_written() { let honcho = checked(SECTION).unwrap().unwrap(); assert_eq!( honcho, HonchoConfig { url: "http://rift.scylla-hammerhead.ts.net:8000".to_string(), agent: "boxmaker0".to_string(), owner: "kyle".to_string(), trusted: "boxmaker0-trusted".to_string(), untrusted: "boxmaker0-untrusted".to_string(), fleet: "fleet".to_string(), read: Vec::new(), keys: BTreeMap::new(), } ); } #[test] fn read_workspaces_default_to_untrusted_and_private_and_keep_their_order() { let honcho = checked(&format!("{SECTION}{READS}")).unwrap().unwrap(); assert_eq!( honcho.read, vec![ ReadWorkspace { agent: "boxmaker1".to_string(), workspace: "boxmaker1-trusted".to_string(), trust: Trust::Untrusted, class: DataClass::Private, }, ReadWorkspace { agent: "boxmaker2".to_string(), workspace: "boxmaker2-trusted".to_string(), trust: Trust::Trusted, class: DataClass::Secret, }, ] ); let written = "[[honcho.read]]\nagent = \"b1\"\nworkspace = \"w1\"\ntrust = \"untrusted\"\nclass = \"public\"\n[[honcho.read]]\nagent = \"b2\"\nworkspace = \"w2\"\nclass = \"private\"\n"; let honcho = checked(&format!("{SECTION}{written}")).unwrap().unwrap(); let labels: Vec<(Trust, DataClass)> = honcho.read.iter().map(|r| (r.trust, r.class)).collect(); assert_eq!( labels, vec![ (Trust::Untrusted, DataClass::Public), (Trust::Untrusted, DataClass::Private) ] ); } #[test] fn every_key_is_checked_by_its_name_and_never_read() { // None of these exists: a key is checked here, and read only when a client is made. let keys = "[secrets.honcho_trusted]\nenv = \"BOXMAKER_TEST_NO_SUCH_VARIABLE\"\n\ [secrets.honcho_untrusted]\nfile = \"/nonexistent/boxmaker/untrusted.key\"\n\ [secrets.honcho_fleet]\ncredential = \"honcho-fleet\"\n\ [secrets.honcho_read_boxmaker2]\nenv = \"HONCHO_READ_2\"\n"; let honcho = checked(&format!("{SECTION}{READS}{keys}")) .unwrap() .unwrap(); let expected: BTreeMap = [ ( "honcho_trusted", SecretSource::Env("BOXMAKER_TEST_NO_SUCH_VARIABLE".to_string()), ), ( "honcho_untrusted", SecretSource::File(PathBuf::from("/nonexistent/boxmaker/untrusted.key")), ), ( "honcho_fleet", SecretSource::Credential("honcho-fleet".to_string()), ), ( "honcho_read_boxmaker2", SecretSource::Env("HONCHO_READ_2".to_string()), ), ] .into_iter() .map(|(name, source)| (name.to_string(), source)) .collect(); assert_eq!(honcho.keys, expected); assert_eq!(honcho.read.len(), 2, "the keys change nothing else"); } #[test] fn the_key_names_and_the_pointer() { assert_eq!(TRUSTED_KEY, "honcho_trusted"); assert_eq!(UNTRUSTED_KEY, "honcho_untrusted"); assert_eq!(FLEET_KEY, "honcho_fleet"); assert_eq!(READ_KEY, "honcho_read_"); assert_eq!(read_key("boxmaker1"), "honcho_read_boxmaker1"); assert_eq!(RUNBOOK, "see docs/runbook.md#honcho-config-invalid"); assert_eq!(MAX_NAME, 512); } #[test] fn unknown_keys_and_wrong_types_are_parse_errors() { for text in [ with("fleet = \"fleet\"", "fleet = \"fleet\"\nfleat = \"x\"", ""), format!("{SECTION}[[honcho.read]]\nagent = \"b1\"\nworkspace = \"w1\"\nlabel = \"x\"\n"), format!("{SECTION}[secrets.honcho_trusted]\nenv = \"K\"\nvalue = \"x\"\n"), format!("{SECTION}[honcho.read]\nagent = \"b1\"\nworkspace = \"w1\"\n"), format!("{SECTION}[[honcho.read]]\nagent = \"b1\"\nworkspace = \"w1\"\ntrust = true\n"), format!("{SECTION}[[honcho.read]]\nagent = \"b1\"\n"), format!("{SECTION}[sockets]\nstates = \"/x\"\n"), with("owner = \"kyle\"\n", "", ""), with("agent = \"boxmaker0\"", "agent = 0", ""), "[secrets]\nhoncho_trusted = \"HONCHO_KEY\"\n".to_string(), ] { assert!(Config::parse(&text).is_err(), "{text}"); } } #[test] fn the_state_socket_is_under_home_unless_set() { let config = Config::parse("[paths]\nhome = \"/srv/bx\"\n").unwrap(); assert_eq!( config.state_socket(), PathBuf::from("/srv/bx/run/memory-broker/state.sock") ); let config = Config::parse("[paths]\nhome = \"/h\"\n[sockets]\nstate = \"/r/s.sock\"\n").unwrap(); assert_eq!(config.state_socket(), PathBuf::from("/r/s.sock")); let config = Config::parse("[paths]\nhome = \"/h\"\n[sockets]\nstate = \"\"\n").unwrap(); assert_eq!( config.state_socket(), PathBuf::from("/h/run/memory-broker/state.sock") ); // The other three do not move. assert_eq!( config.loop_socket(), PathBuf::from("/h/run/loop-memory/memory.sock") ); } #[test] fn load_refuses_an_invalid_section_and_keeps_a_valid_one() { let dir = TempDir::new("honcho-load"); let good = dir.write("good.toml", &format!("{SECTION}{READS}")); let config = Config::load(&good).unwrap(); assert_eq!(config.honcho_config().unwrap().unwrap().read.len(), 2); let bad = dir.write( "bad.toml", &with("owner = \"kyle\"", "owner = \"boxmaker0\"", ""), ); let err = Config::load(&bad).unwrap_err(); let why = "[honcho] agent and owner are both \"boxmaker0\": the agent's peer must not be the owner's"; assert!( matches!(&err, ConfigError::Honcho(path, reason) if path == &bad && reason == why), "{err:?}" ); assert_eq!(err.to_string(), format!("{}: {why}", bad.display())); let unknown = dir.write( "unknown.toml", &with("fleet = \"fleet\"", "fleet = \"fleet\"\nfleat = \"x\"", ""), ); assert!( matches!(Config::load(&unknown), Err(ConfigError::Parse(..))), "an unknown key stays a parse error" ); }