230 lines
8.0 KiB
Rust
230 lines
8.0 KiB
Rust
//! `[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<Option<HonchoConfig>, 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<String, SecretSource> = [
|
|
(
|
|
"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"
|
|
);
|
|
}
|