//! The `podman` argument lists, as golden files: one argument per line, compared exactly. A //! runtime that only builds the list stands in for Podman, so the lists are built from real //! `RunSpec`s, which only `runner::run` can make. Do not edit. #[path = "support/build.rs"] mod build; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::sync::Mutex; use brokerd::config::Runner; use brokerd::podman::{container_name, egress_args, tool_args}; use brokerd::policy::{Outcome, SessionState, decide}; use brokerd::runner::{RunError, RunOutput, RunSpec, Runtime, run}; use build::{fetch, grant, now, read, request, set, shell, write}; use proto::{CallId, Mode, SessionId, ToolRequest}; const IMAGE: &str = "localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; fn runner() -> Runner { let text = format!("[runner]\nimage = \"{IMAGE}\"\n"); brokerd::config::Config::parse(&text) .unwrap() .runner .unwrap() } /// Builds the tool's argument list inside `run`, as the real runtime will. struct Lists { egress: Option, got: Mutex>>, } impl Runtime for Lists { fn run(&self, spec: &RunSpec) -> Result { let name = container_name(spec.session(), spec.call(), 7); let args = tool_args(spec, &runner(), &name, self.egress.as_deref()); self.got.lock().unwrap().push(args); Ok(RunOutput { content: String::new(), truncated: false, }) } } fn list_for(grants: Vec, req: ToolRequest, egress: Option<&Path>) -> Vec { let decision = match decide(req, &set(grants), SessionState::default(), now()) { Outcome::Allowed(d) => d, other => panic!("not allowed: {other:?}"), }; let lists = Lists { egress: egress.map(Path::to_path_buf), got: Mutex::new(Vec::new()), }; run(decision, &lists); let got = lists.got.into_inner().unwrap(); assert_eq!(got.len(), 1); got[0] .iter() .map(|a| a.to_str().unwrap().to_string()) .collect() } fn golden(name: &str) -> Vec { let path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/podman") .join(name); let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display())); text.lines().map(str::to_string).collect() } #[test] fn the_container_name_says_whose_call_it_is() { let s = SessionId::new("chat-17").unwrap(); assert_eq!(container_name(&s, CallId(42), 3), "boxmaker-chat-17-42-3"); } #[test] fn read_file_gets_its_one_directory_read_only_and_no_network() { let got = list_for( vec![grant("notes", "read_file", Mode::Auto).paths(&["/home/kyle/notes"])], read("/home/kyle/notes/a.md"), None, ); assert_eq!(got, golden("read_file.args")); } #[test] fn write_file_gets_its_one_directory_writable() { let got = list_for( vec![grant("out", "write_file", Mode::Auto).paths(&["/home/kyle/out"])], write("/home/kyle/out/b.md"), None, ); assert_eq!(got, golden("write_file.args")); } #[test] fn shell_gets_every_grant_directory_writable_in_order() { let got = list_for( vec![grant("sh", "shell", Mode::Auto).paths(&["/srv/a", "/srv/b"])], shell(Some("/srv/b")), None, ); assert_eq!(got, golden("shell.args")); let bare = list_for( vec![grant("sh", "shell", Mode::Auto)], request("shell", r#"{"command":"ls"}"#), None, ); assert_eq!(bare, golden("shell_no_paths.args")); } #[test] fn http_fetch_gets_the_egress_directory_and_still_no_network() { let got = list_for( vec![grant("web", "http_fetch", Mode::Auto).hosts(&["example.com"])], fetch("https://example.com/a"), Some(Path::new("/h/run/egress/boxmaker-s1-1-7")), ); assert_eq!(got, golden("http_fetch.args")); assert!(got.contains(&"--network=none".to_string())); } #[test] fn the_proxy_gets_a_network_the_socket_and_the_hosts() { let got: Vec = egress_args( &runner(), "boxmaker-s1-1-7", Path::new("/h/run/egress/boxmaker-s1-1-7"), &["example.com".to_string(), "*.example.org".to_string()], ) .iter() .map(|a| a.to_str().unwrap().to_string()) .collect(); assert_eq!(got, golden("egress.args")); } #[test] fn no_argument_holds_a_shell_string_or_a_second_network() { let got = list_for( vec![grant("sh", "shell", Mode::Auto).paths(&["/srv/a"])], request( "shell", r#"{"command":"curl evil.test; rm -rf /","cwd":"/srv/a"}"#, ), None, ); assert!( got.iter().all(|a| !a.contains("evil.test")), "the command goes on standard input" ); assert_eq!(got.iter().filter(|a| a.starts_with("--network")).count(), 1); }