Give rootless qualification hosts unique identities

This commit is contained in:
rcourtman
2026-09-01 21:03:03 +01:00
parent 4fda8f146e
commit bc3dd3b704
4 changed files with 25 additions and 0 deletions
@@ -7324,6 +7324,10 @@ or granting an outer default route is invalid qualification. The outer host
uses a private cgroup namespace with no host cgroup bind mount. Each dedicated
runtime identity must have an active delegated systemd user manager before its
daemon starts, and cleanup must stop that manager and remove its linger state.
Before each disposable host's first systemd boot, the wrapper installs a fresh
valid machine ID into that stopped container. The combined receipt validator
rejects a Docker and Podman pair that reports the same machine identity; a
shared image fallback identity cannot stand in for two independent hosts.
The in-container control-plane fixture must implement the same authenticated
collector-uninstall response contract as the production lifecycle client. A
teardown succeeds only after the fixture records the exact registered binding
@@ -259,6 +259,9 @@ the immutable schema-v7 systemd packet. The opt-in
`scripts/run-secure-runtime-rootless-qualification.sh` wrapper creates fresh,
network-isolated Ubuntu/systemd hosts for real rootless Docker and Podman,
never mounts a host daemon socket, and records each host identity separately.
It installs a fresh valid machine ID into each stopped container before first
systemd boot, and the combined validator rejects duplicate Docker and Podman
machine identities rather than accepting a shared image-derived fallback.
The exact qualification packet remains outside the image layers; the image
must pre-create its root-owned, mode-`0700` packet destination before the
wrapper injects artifacts into each stopped disposable container. The image
@@ -1856,6 +1856,7 @@ func TestRootlessQualificationGuardAndWrapperInvariants(t *testing.T) {
`docker cp "${container_id}:/opt/pulse/result/rootless-receipt.json"`,
`capture_qualification_container_diagnostics`, `journalctl --no-pager -n 2000`,
`302a300506032b6570032100`, `len(spki) != len(prefix) + 32`,
`machine_id="$(openssl rand -hex 16)"`, `docker cp "${machine_id_file}" "${container_id}:/etc/machine-id"`,
} {
if !strings.Contains(script, required) {
t.Fatalf("rootless qualification wrapper missing %q", required)
@@ -1882,6 +1883,11 @@ func TestRootlessQualificationGuardAndWrapperInvariants(t *testing.T) {
if packetDirectoryIndex < 0 || packetCopyIndex < 0 || packetCopyIndex < packetDirectoryIndex {
t.Fatal("rootless wrapper must create the private packet destination in the image before artifact injection")
}
machineIDCopyIndex := strings.Index(script, `docker cp "${machine_id_file}" "${container_id}:/etc/machine-id"`)
containerStartIndex := strings.Index(script, `docker start "${container_id}"`)
if machineIDCopyIndex < 0 || containerStartIndex < 0 || machineIDCopyIndex > packetCopyIndex || machineIDCopyIndex > containerStartIndex {
t.Fatal("rootless wrapper must install a per-host machine ID before packet injection and first systemd boot")
}
}
func TestRootlessQualificationDockerCommandUsesSupportedNetworkDriver(t *testing.T) {
@@ -234,6 +234,16 @@ run_runtime() {
local container_name="pulse-rootless-qual-${runtime_name}-${SOURCE_COMMIT:0:8}-$$"
local container_id
local local_receipt="${OUTPUT_DIR}/${runtime_name}-receipt.json"
local machine_id_file="${PACKET_DIR}/.machine-id-${runtime_name}"
local machine_id
machine_id="$(openssl rand -hex 16)"
if [[ ! "${machine_id}" =~ ^[0-9a-f]{32}$ || "${machine_id}" == "00000000000000000000000000000000" ]]; then
echo "ERROR: unable to generate a valid machine ID for ${runtime_name} qualification" >&2
return 1
fi
printf '%s\n' "${machine_id}" >"${machine_id_file}"
chmod 0444 "${machine_id_file}"
container_id="$(docker create --name "${container_name}" --hostname "pulse-rootless-${runtime_name}" \
--label "${CONTAINER_RUN_LABEL}=${CONTAINER_RUN_NONCE}" \
@@ -241,6 +251,8 @@ run_runtime() {
--tmpfs /run:rw,nosuid,nodev,mode=755 --tmpfs /run/lock:rw,nosuid,nodev,mode=755 \
"${IMAGE_TAG}")"
CONTAINER_IDS+=("${container_id}")
docker cp "${machine_id_file}" "${container_id}:/etc/machine-id"
rm -f -- "${machine_id_file}"
docker cp "${PACKET_DIR}/." "${container_id}:/opt/pulse/packet"
docker start "${container_id}" >/dev/null