arcbox_computer_runtime/config/
jailer.rs1use std::path::{Path, PathBuf};
2
3use arcbox_vm_driver::{CgroupSpec, IsolationSpec};
4use serde::{Deserialize, Serialize};
5
6use crate::error::ComputerError;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct JailerConfig {
17 pub uid: u32,
19 pub gid: u32,
21 pub chroot_base_dir: Option<String>,
23 pub netns: Option<String>,
25 #[serde(default)]
27 pub new_pid_ns: bool,
28 pub cgroup_version: Option<String>,
30 pub parent_cgroup: Option<String>,
32}
33
34impl JailerConfig {
35 pub const DEFAULT_CHROOT_BASE: &'static str = "/srv/jailer";
37
38 pub fn chroot_base(&self) -> &Path {
41 Path::new(
42 self.chroot_base_dir
43 .as_deref()
44 .unwrap_or(Self::DEFAULT_CHROOT_BASE),
45 )
46 }
47}
48
49impl TryFrom<&JailerConfig> for IsolationSpec {
50 type Error = ComputerError;
51
52 fn try_from(jc: &JailerConfig) -> Result<Self, ComputerError> {
59 let cgroup = match (jc.cgroup_version.as_deref(), jc.parent_cgroup.as_deref()) {
60 (None, None) => None,
61 (version, parent) => Some(CgroupSpec {
62 version: match version {
63 None => 1,
64 Some("1") => 1,
65 Some("2") => 2,
66 Some(other) => {
67 return Err(ComputerError::Config(format!(
68 "jailer cgroup_version must be \"1\" or \"2\", got {other:?}"
69 )));
70 }
71 },
72 parent: parent.map(str::to_owned),
73 }),
74 };
75 Ok(Self::Jailer {
76 uid: jc.uid,
77 gid: jc.gid,
78 chroot_base: jc.chroot_base().to_path_buf(),
79 netns: jc.netns.as_deref().map(PathBuf::from),
80 new_pid_ns: jc.new_pid_ns,
81 cgroup,
82 })
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn jailer() -> JailerConfig {
91 JailerConfig {
92 uid: 1000,
93 gid: 1000,
94 chroot_base_dir: None,
95 netns: None,
96 new_pid_ns: false,
97 cgroup_version: None,
98 parent_cgroup: None,
99 }
100 }
101
102 #[test]
103 fn isolation_defaults_the_chroot_base_and_carries_no_cgroup_when_unset() {
104 let spec = IsolationSpec::try_from(&jailer()).unwrap();
105 assert_eq!(
106 spec,
107 IsolationSpec::Jailer {
108 uid: 1000,
109 gid: 1000,
110 chroot_base: PathBuf::from("/srv/jailer"),
111 netns: None,
112 new_pid_ns: false,
113 cgroup: None,
114 }
115 );
116 }
117
118 #[test]
119 fn isolation_maps_the_cgroup_fields_and_the_jailer_default_version() {
120 let mut jc = jailer();
121 jc.chroot_base_dir = Some("/jail".into());
122 jc.netns = Some("/var/run/netns/x".into());
123 jc.new_pid_ns = true;
124 jc.cgroup_version = Some("2".into());
125 jc.parent_cgroup = Some("arcbox".into());
126 let IsolationSpec::Jailer {
127 chroot_base,
128 netns,
129 new_pid_ns,
130 cgroup,
131 ..
132 } = IsolationSpec::try_from(&jc).unwrap()
133 else {
134 panic!("jailer config converts to jailer isolation");
135 };
136 assert_eq!(chroot_base, PathBuf::from("/jail"));
137 assert_eq!(netns, Some(PathBuf::from("/var/run/netns/x")));
138 assert!(new_pid_ns);
139 assert_eq!(
140 cgroup,
141 Some(CgroupSpec {
142 version: 2,
143 parent: Some("arcbox".into())
144 })
145 );
146
147 jc.cgroup_version = None;
149 let IsolationSpec::Jailer { cgroup, .. } = IsolationSpec::try_from(&jc).unwrap() else {
150 unreachable!()
151 };
152 assert_eq!(cgroup.map(|c| c.version), Some(1));
153
154 jc.cgroup_version = Some("v2".into());
155 assert!(matches!(
156 IsolationSpec::try_from(&jc),
157 Err(ComputerError::Config(_))
158 ));
159 }
160}