From 735fe4e45262a08acf7664f5a16e9ab013a28af7 Mon Sep 17 00:00:00 2001 From: Aaron Paterson Date: Sun, 2 Aug 2026 01:43:43 +0000 Subject: [PATCH] Show that a pod's containers each run as their own unprivileged user Nothing in the pod requires its containers to be root, and each carries its own process configuration, so they can run under different users without anything else being arranged. --- Sources/Integration/PodTests.swift | 44 ++++++++++++++++++++++++++++++ Sources/Integration/Suite.swift | 1 + 2 files changed, 45 insertions(+) diff --git a/Sources/Integration/PodTests.swift b/Sources/Integration/PodTests.swift index ae1caec86..148dca190 100644 --- a/Sources/Integration/PodTests.swift +++ b/Sources/Integration/PodTests.swift @@ -96,6 +96,50 @@ extension IntegrationSuite { } } + func testPodRootlessContainers() async throws { + let id = "test-pod-rootless-containers" + let bs = try await bootstrap(id) + + let pod = try LinuxPod(id, vmm: bs.vmm) { config in + config.cpus = 2 + config.memoryInBytes = 512.mib() + config.bootLog = bs.bootLog + } + + // Each container in the pod runs as its own unprivileged user, which + // needs nothing of the guest beyond the ids themselves. + let ids: [(String, UInt32)] = [("rootless1", 1000), ("rootless2", 1001)] + let buffers = [ids[0].0: BufferWriter(), ids[1].0: BufferWriter()] + for (name, uid) in ids { + let buffer = buffers[name]! + try await pod.addContainer( + name, + rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: name) + ) { config in + config.process.arguments = ["/bin/sh", "-c", "id -u"] + config.process.user = ContainerizationOCI.User(uid: uid, gid: uid) + config.process.stdout = buffer + } + } + + try await pod.create() + + for (name, uid) in ids { + try await pod.startContainer(name) + let status = try await pod.waitContainer(name) + guard status.exitCode == 0 else { + throw IntegrationError.assert(msg: "\(name) status \(status) != 0") + } + let out = (String(data: buffers[name]!.data, encoding: .utf8) ?? "") + .trimmingCharacters(in: .whitespacesAndNewlines) + guard out == "\(uid)" else { + throw IntegrationError.assert(msg: "\(name) ran as '\(out)', expected \(uid)") + } + } + + try await pod.stop() + } + func testPodContainerOutput() async throws { let id = "test-pod-container-output" diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index 9dae84351..a464fd295 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -531,6 +531,7 @@ struct IntegrationSuite: AsyncParsableCommand { // Pods Test("pod single container", testPodSingleContainer), Test("pod multiple containers", testPodMultipleContainers), + Test("pod rootless containers", testPodRootlessContainers), Test("pod container output", testPodContainerOutput), Test("pod concurrent containers", testPodConcurrentContainers), Test("pod exec in container", testPodExecInContainer),