//! The runner seam: what `run` puts in the `RunSpec` for each tool, and what it answers. Do not //! edit. #[path = "support/build.rs"] mod build; #[path = "support/runtime.rs"] mod runtime; use brokerd::args::{ToolArgs, ToolName}; use brokerd::policy::{Decision, Outcome, SessionState, decide}; use brokerd::runner::{Mount, REFUSING, Refusing, RunError, RunOutput, run}; use build::{grant, now, read, request, set}; use proto::{DataClass, Mode, ToolRequest, ToolResponse}; use runtime::Recording; fn allowed(grants: Vec, request: ToolRequest) -> Decision { match decide(request, &set(grants), SessionState::default(), now()) { Outcome::Allowed(decision) => decision, other => panic!("the test's call is not allowed: {other:?}"), } } fn mount(path: &str, writable: bool) -> Mount { Mount { path: path.to_string(), writable, } } #[test] fn read_file_mounts_the_matched_path_read_only_and_has_no_network() { let d = allowed( vec![grant("notes", "read_file", Mode::Auto).paths(&["/h/notes", "/h/notes/deep"])], read("/h/notes/deep/a.md"), ); let rt = Recording::answering("text"); run(d, rt.as_ref()); let seen = rt.seen(); assert_eq!(seen.len(), 1); assert_eq!(seen[0].tool, ToolName::ReadFile); assert_eq!( seen[0].arguments, ToolArgs::ReadFile { path: "/h/notes/deep/a.md".to_string() } ); // The longest path that holds the argument, and only that one. assert_eq!(seen[0].mounts, [mount("/h/notes/deep", false)]); assert_eq!(seen[0].egress, None); } #[test] fn write_file_mounts_the_matched_path_writable() { // A grant path equal to the argument does not count, so `/s/out` is written through `/s`. let d = allowed( vec![grant("s", "write_file", Mode::Auto).paths(&["/s", "/s/out"])], request("write_file", r#"{"path":"/s/out","content":"x"}"#), ); let rt = Recording::answering(""); run(d, rt.as_ref()); let seen = rt.seen(); assert_eq!(seen[0].tool, ToolName::WriteFile); assert_eq!(seen[0].mounts, [mount("/s", true)]); assert_eq!(seen[0].egress, None); } #[test] fn shell_mounts_every_path_of_the_grant_writable() { let d = allowed( vec![grant("sh", "shell", Mode::Auto).paths(&["/a", "/b/c"])], request("shell", r#"{"command":"ls","cwd":"/b/c/d"}"#), ); let rt = Recording::answering(""); run(d, rt.as_ref()); let seen = rt.seen(); assert_eq!(seen[0].tool, ToolName::Shell); assert_eq!(seen[0].mounts, [mount("/a", true), mount("/b/c", true)]); assert_eq!(seen[0].egress, None); } #[test] fn shell_without_paths_mounts_nothing() { let d = allowed( vec![grant("sh", "shell", Mode::Auto)], request("shell", r#"{"command":"date"}"#), ); let rt = Recording::answering(""); run(d, rt.as_ref()); let seen = rt.seen(); assert_eq!(seen[0].mounts, []); assert_eq!(seen[0].egress, None); } #[test] fn http_fetch_mounts_nothing_and_may_reach_the_grants_hosts_only() { let d = allowed( vec![grant("web", "http_fetch", Mode::Auto).hosts(&["example.com", "*.example.org"])], request("http_fetch", r#"{"url":"https://www.example.org/x"}"#), ); let rt = Recording::answering(""); run(d, rt.as_ref()); let seen = rt.seen(); assert_eq!(seen[0].tool, ToolName::HttpFetch); assert_eq!(seen[0].mounts, []); assert_eq!( seen[0].egress, Some(vec!["example.com".to_string(), "*.example.org".to_string()]) ); } #[test] fn a_result_carries_the_label_combined_over_every_matching_grant() { // `b-keys` has the longer path and wins the mount; the label is the highest class of both // grants, and untrusted because `a-home` says so. let d = allowed( vec![ grant("a-home", "read_file", Mode::Auto) .paths(&["/home/kyle"]) .class(DataClass::Private), grant("b-keys", "read_file", Mode::Auto) .paths(&["/home/kyle/keys"]) .class(DataClass::Secret) .trusted(), ], read("/home/kyle/keys/id"), ); let rt = Recording::with(Ok(RunOutput { content: "key".to_string(), truncated: true, })); let answer = run(d, rt.as_ref()); assert_eq!( answer, ToolResponse::Result { content: "key".to_string(), class: DataClass::Secret, untrusted: true, truncated: true, } ); assert_eq!(rt.seen()[0].mounts, [mount("/home/kyle/keys", false)]); } #[test] fn a_run_error_is_a_failure_with_the_runtimes_sentence() { for error in [ RunError::Failed("the tool timed out".to_string()), RunError::Unavailable("the container could not start".to_string()), ] { let d = allowed( vec![grant("n", "read_file", Mode::Auto).paths(&["/n"])], read("/n/a"), ); let text = match &error { RunError::Failed(t) | RunError::Unavailable(t) => t.clone(), }; let rt = Recording::with(Err(error)); assert_eq!(run(d, rt.as_ref()), ToolResponse::Failed { message: text }); } } #[test] fn the_production_runtime_refuses_every_call() { assert_eq!(REFUSING, "the runner arrives in M3b"); let d = allowed( vec![grant("n", "read_file", Mode::Auto).paths(&["/n"])], read("/n/a"), ); assert_eq!( run(d, &Refusing), ToolResponse::Failed { message: REFUSING.to_string() } ); }