Skip to content

Commit ae4a493

Browse files
Gamnaam SongG4614
authored andcommitted
feat(box): add privileged plumbing for dind
1 parent 4dde734 commit ae4a493

1 file changed

Lines changed: 80 additions & 10 deletions

File tree

src/guest/src/container/spec.rs

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ pub fn create_oci_spec(
5959
tty: bool,
6060
) -> BoxliteResult<Spec> {
6161
let caps = build_capabilities(cap_overrides)?;
62+
let privileged = cap_overrides
63+
.iter()
64+
.any(|c| c.enabled && (c.name == "ALL" || c.name == "SYS_ADMIN"));
6265
let namespaces = build_default_namespaces()?;
63-
let mut mounts = build_standard_mounts(bundle_path)?;
66+
let mut mounts = build_standard_mounts(bundle_path, privileged)?;
6467

6568
// Add user-specified bind mounts
6669
for user_mount in user_mounts {
@@ -95,7 +98,7 @@ pub fn create_oci_spec(
9598

9699
let process = build_process_spec(entrypoint, env, workdir, uid, gid, caps, tty)?;
97100
let root = build_root_spec(rootfs)?;
98-
let linux = build_linux_spec(container_id, namespaces)?;
101+
let linux = build_linux_spec(container_id, namespaces, privileged)?;
99102

100103
SpecBuilder::default()
101104
.version("1.0.2")
@@ -460,6 +463,7 @@ fn build_root_spec(rootfs: &str) -> BoxliteResult<oci_spec::runtime::Root> {
460463
fn build_linux_spec(
461464
container_id: &str,
462465
namespaces: Vec<oci_spec::runtime::LinuxNamespace>,
466+
privileged: bool,
463467
) -> BoxliteResult<oci_spec::runtime::Linux> {
464468
// UID/GID mappings for user namespace
465469
// Map full range of UIDs/GIDs to allow non-root users (nginx=33, etc.)
@@ -507,19 +511,28 @@ fn build_linux_spec(
507511
// let cgroups_path = format!("/boxlite/{}", container_id);
508512
let _ = container_id; // Suppress unused warning
509513

510-
LinuxBuilder::default()
514+
let mut builder = LinuxBuilder::default()
511515
.namespaces(namespaces)
512516
.uid_mappings(uid_mappings)
513-
.gid_mappings(gid_mappings)
514-
// .masked_paths(masked_paths)
515-
// .readonly_paths(readonly_paths)
516-
// .cgroups_path(cgroups_path)
517+
.gid_mappings(gid_mappings);
518+
// .cgroups_path(cgroups_path)
519+
520+
if privileged {
521+
// DinD writes /proc/sys/net/ipv4/ip_forward when bringing up its
522+
// bridge. Clearing these lists matches the privileged-container shape
523+
// needed for that path while non-privileged boxes keep OCI defaults.
524+
builder = builder
525+
.masked_paths(Vec::<String>::new())
526+
.readonly_paths(Vec::<String>::new());
527+
}
528+
529+
builder
517530
.build()
518531
.map_err(|e| BoxliteError::Internal(format!("Failed to build linux spec: {}", e)))
519532
}
520533

521534
/// Build standard mounts for container filesystem
522-
fn build_standard_mounts(bundle_path: &Path) -> BoxliteResult<Vec<Mount>> {
535+
fn build_standard_mounts(bundle_path: &Path, cgroup_rw: bool) -> BoxliteResult<Vec<Mount>> {
523536
let mut mounts = vec![
524537
// /proc - Process information
525538
MountBuilder::default()
@@ -610,6 +623,27 @@ fn build_standard_mounts(bundle_path: &Path) -> BoxliteResult<Vec<Mount>> {
610623
// .map_err(|e| {
611624
// BoxliteError::Internal(format!("Failed to build /sys/fs/cgroup mount: {}", e))
612625
// })?,
626+
];
627+
628+
if cgroup_rw {
629+
mounts.push(
630+
MountBuilder::default()
631+
.destination("/sys/fs/cgroup")
632+
.typ("cgroup2")
633+
.source("cgroup2")
634+
.options(vec![
635+
"nosuid".to_string(),
636+
"noexec".to_string(),
637+
"nodev".to_string(),
638+
])
639+
.build()
640+
.map_err(|e| {
641+
BoxliteError::Internal(format!("Failed to build cgroup2 mount: {}", e))
642+
})?,
643+
);
644+
}
645+
646+
mounts.extend(vec![
613647
// /tmp - Temporary filesystem
614648
MountBuilder::default()
615649
.destination("/tmp")
@@ -622,7 +656,7 @@ fn build_standard_mounts(bundle_path: &Path) -> BoxliteResult<Vec<Mount>> {
622656
])
623657
.build()
624658
.map_err(|e| BoxliteError::Internal(format!("Failed to build /tmp mount: {}", e)))?,
625-
];
659+
]);
626660

627661
// Bind-mount /etc/hostname, /etc/hosts, /etc/resolv.conf from the bundle
628662
// dir into the container. Uses rbind + rprivate (matching Docker defaults).
@@ -1292,7 +1326,7 @@ mod tests {
12921326

12931327
#[test]
12941328
fn linux_spec_keeps_default_proc_sys_hardening() {
1295-
let spec = build_linux_spec("c", build_default_namespaces().unwrap()).unwrap();
1329+
let spec = build_linux_spec("c", build_default_namespaces().unwrap(), false).unwrap();
12961330
let ro = spec
12971331
.readonly_paths()
12981332
.as_ref()
@@ -1302,4 +1336,40 @@ mod tests {
13021336
"security options must not implicitly make /proc/sys writable; got {ro:?}"
13031337
);
13041338
}
1339+
1340+
#[test]
1341+
fn privileged_linux_spec_clears_readonly_and_masked_paths() {
1342+
let spec = build_linux_spec("c", build_default_namespaces().unwrap(), true).unwrap();
1343+
assert_eq!(
1344+
spec.readonly_paths().as_ref().map(Vec::len),
1345+
Some(0),
1346+
"privileged containers need writable /proc/sys for DinD bridge setup"
1347+
);
1348+
assert_eq!(
1349+
spec.masked_paths().as_ref().map(Vec::len),
1350+
Some(0),
1351+
"privileged containers should clear masked paths"
1352+
);
1353+
}
1354+
1355+
#[test]
1356+
fn privileged_standard_mounts_add_cgroup2() {
1357+
let dir = tempfile::TempDir::new().unwrap();
1358+
let mounts = build_standard_mounts(dir.path(), true).unwrap();
1359+
assert!(
1360+
mounts
1361+
.iter()
1362+
.any(|m| m.destination() == "/sys/fs/cgroup"
1363+
&& m.typ().as_deref() == Some("cgroup2")),
1364+
"privileged DinD path must mount writable cgroup2"
1365+
);
1366+
1367+
let unprivileged_mounts = build_standard_mounts(dir.path(), false).unwrap();
1368+
assert!(
1369+
unprivileged_mounts
1370+
.iter()
1371+
.all(|m| m.destination() != "/sys/fs/cgroup"),
1372+
"default boxes should not pay the cgroup2 mount cost"
1373+
);
1374+
}
13051375
}

0 commit comments

Comments
 (0)