test(podman): make rootful userns and cgroup checks pass

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
This commit is contained in:
Drew Newberry
2026-09-24 18:59:22 -07:00
parent a110287edc
commit b7cce5fe1d
3 changed files with 69 additions and 15 deletions
+23 -3
View File
@@ -16,6 +16,16 @@
use openshell_e2e::harness::sandbox::SandboxGuard;
const CGROUP_READ_POLICY: &str = r"version: 1
filesystem_policy:
include_workdir: true
read_only: [/usr, /lib, /lib64, /proc, /etc, /sys/fs/cgroup]
read_write: [/sandbox, /tmp, /dev/null]
landlock:
compatibility: best_effort
network_policies: {}
";
const CPU_REQUEST: &str = "500m";
const MEMORY_REQUEST: &str = "512Mi";
@@ -39,9 +49,19 @@ async fn sandbox_resource_limits_are_enforced_via_cgroups() {
return;
}
let mut sandbox = SandboxGuard::create(&["--cpu", CPU_REQUEST, "--memory", MEMORY_REQUEST])
.await
.expect("sandbox create with resource limits should succeed");
let policy = tempfile::NamedTempFile::new().expect("create cgroup read policy file");
std::fs::write(policy.path(), CGROUP_READ_POLICY).expect("write cgroup read policy");
let policy_path = policy.path().to_str().expect("policy path is UTF-8");
let mut sandbox = SandboxGuard::create(&[
"--cpu",
CPU_REQUEST,
"--memory",
MEMORY_REQUEST,
"--policy",
policy_path,
])
.await
.expect("sandbox create with resource limits should succeed");
let memory_max = sandbox
.exec(&["cat", "/sys/fs/cgroup/memory.max"])
@@ -9,6 +9,18 @@
- name: Wait for SSH
ansible.builtin.wait_for_connection:
# Rootful Podman's --userns auto allocates IDs from the "containers"
# subordinate ranges, which the Fedora cloud image does not define.
- name: Configure rootful Podman subordinate IDs
become: true
ansible.builtin.lineinfile:
path: "{{ item }}"
regexp: '^containers:'
line: 'containers:1000000:65536'
loop:
- /etc/subuid
- /etc/subgid
- name: Enable rootful Podman socket
become: true
ansible.builtin.systemd_service:
@@ -86,16 +86,38 @@ async fn configured_userns_matches_podman_reference() {
}
fn normalize_uid_map(value: &str) -> Option<String> {
let mappings = value
.lines()
.filter_map(|line| {
let fields = line.split_whitespace().collect::<Vec<_>>();
(fields.len() == 3
&& fields
.iter()
.all(|field| field.bytes().all(|byte| byte.is_ascii_digit())))
.then(|| fields.join(" "))
})
.collect::<Vec<_>>();
(!mappings.is_empty()).then(|| mappings.join("\n"))
let mut mappings: Vec<(u64, u64, u64)> = Vec::new();
for line in value.lines() {
let fields = line
.split_whitespace()
.map(str::parse::<u64>)
.collect::<Result<Vec<_>, _>>();
let Ok(fields) = fields else { continue };
let [inside, outside, length] = fields.as_slice() else {
continue;
};
if let Some(previous) = mappings.last_mut()
&& previous.0.checked_add(previous.2) == Some(*inside)
&& previous.1.checked_add(previous.2) == Some(*outside)
{
previous.2 += *length;
} else {
mappings.push((*inside, *outside, *length));
}
}
(!mappings.is_empty()).then(|| {
mappings
.iter()
.map(|(inside, outside, length)| format!("{inside} {outside} {length}"))
.collect::<Vec<_>>()
.join("\n")
})
}
#[test]
fn adjacent_uid_ranges_match_a_combined_mapping() {
assert_eq!(
normalize_uid_map("0 0 1\n1 1 65535\n"),
normalize_uid_map("0 0 65536\n")
);
}