brokerd: the podman argument lists
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
run
|
||||
-d
|
||||
--rm
|
||||
--name=boxmaker-s1-1-7-egress
|
||||
--label=boxmaker=egress
|
||||
--network=pasta
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=64
|
||||
--memory=128m
|
||||
--volume=/h/run/egress/boxmaker-s1-1-7:/run/egress:rw
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
egress-proxy
|
||||
--socket
|
||||
/run/egress/egress.sock
|
||||
--allow
|
||||
example.com,*.example.org
|
||||
@@ -0,0 +1,17 @@
|
||||
run
|
||||
--rm
|
||||
-i
|
||||
--name=boxmaker-s1-1-7
|
||||
--label=boxmaker=tool
|
||||
--network=none
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=128
|
||||
--memory=512m
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
--volume=/h/run/egress/boxmaker-s1-1-7:/run/egress:rw
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
http_fetch
|
||||
@@ -0,0 +1,17 @@
|
||||
run
|
||||
--rm
|
||||
-i
|
||||
--name=boxmaker-s1-1-7
|
||||
--label=boxmaker=tool
|
||||
--network=none
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=128
|
||||
--memory=512m
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
--volume=/home/kyle/notes:/home/kyle/notes:ro
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
read_file
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
run
|
||||
--rm
|
||||
-i
|
||||
--name=boxmaker-s1-1-7
|
||||
--label=boxmaker=tool
|
||||
--network=none
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=128
|
||||
--memory=512m
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
--volume=/srv/a:/srv/a:rw
|
||||
--volume=/srv/b:/srv/b:rw
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
shell
|
||||
@@ -0,0 +1,16 @@
|
||||
run
|
||||
--rm
|
||||
-i
|
||||
--name=boxmaker-s1-1-7
|
||||
--label=boxmaker=tool
|
||||
--network=none
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=128
|
||||
--memory=512m
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
shell
|
||||
@@ -0,0 +1,17 @@
|
||||
run
|
||||
--rm
|
||||
-i
|
||||
--name=boxmaker-s1-1-7
|
||||
--label=boxmaker=tool
|
||||
--network=none
|
||||
--read-only
|
||||
--cap-drop=all
|
||||
--security-opt=no-new-privileges
|
||||
--userns=keep-id
|
||||
--pids-limit=128
|
||||
--memory=512m
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
--volume=/home/kyle/out:/home/kyle/out:rw
|
||||
localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
/bin/toolkit
|
||||
write_file
|
||||
@@ -0,0 +1,155 @@
|
||||
//! 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<PathBuf>,
|
||||
got: Mutex<Vec<Vec<OsString>>>,
|
||||
}
|
||||
|
||||
impl Runtime for Lists {
|
||||
fn run(&self, spec: &RunSpec) -> Result<RunOutput, RunError> {
|
||||
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<build::Build>, req: ToolRequest, egress: Option<&Path>) -> Vec<String> {
|
||||
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<String> {
|
||||
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<String> = 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);
|
||||
}
|
||||
Reference in New Issue
Block a user