feat(runtime): user-namespace remap behind a default-off flag (#126, phase 1) - #181
Open
luthermonson wants to merge 2 commits into
Open
feat(runtime): user-namespace remap behind a default-off flag (#126, phase 1)#181luthermonson wants to merge 2 commits into
luthermonson wants to merge 2 commits into
Conversation
…phase 1)
Adds the mechanism to run each runner container in a REMAPPED user
namespace so container uid 0 maps to a high, unprivileged host uid
instead of real host root. This is the structural layer beneath the
capability/seccomp/AppArmor containment: with it on, a container escape
(runc CVE, kernel LPE, the dind bind TOCTOU) lands as an unprivileged
host uid owning nothing, not as root on the VM.
Phase 1 — the core, safe to land:
- config: [runtime.userns] { enabled, base_uid, base_gid, size },
DEFAULT OFF. Resolved() supplies base 1000000000 / size 65536 so an
enabled-but-unconfigured pool can never map container root back onto
host uid 0 (which would be no remap at all).
- spec: oci.WithUserNamespace(uid/gid maps) via usernsSpecOpts, Linux
only, appended with the other spec-hardening opts.
- snapshot: client.WithRemapperLabels on WithNewSnapshot so the rootfs
snapshot presents mapped ownership (idmapped mount where the overlay
snapshotter supports it). FAIL-CLOSED: a host that cannot honor the
remap fails the snapshot prepare and the job errors, rather than
silently running unmapped with container root == host root.
Default-off means this is a no-op for every existing pool. Enable it on
one pool to validate on real hardware, per the issue.
NOT yet covered (follow-up, gated behind the same flag, needs on-metal
validation): the per-job runner overlay lowerdir ownership for
custom-image jobs, dind sibling-container mapping consistency and the
bind-source chown in pkg/dind, and the macOS virtio-fs path. Standard
Linux image jobs (runner pre-installed in the image, no runner-mount
overlay) are covered by the snapshot remap alone.
Tests: usernsSpecOpts mapping/namespace assertions (incl. the
resolved-default guard against a degenerate map onto uid 0); config
load + Resolved defaults + the load-bearing default-off assertion.
Review of #181 found the Userns field was only threaded into ONE of the two runtime.New(runtime.Config{}) construction sites — the in-VM worker branch. The host/native-Linux serve path (cmd/ephemerd/main.go:641), which is what actually runs job containers on a Proxmox Linux node, was missing it. A prior replace_all matched only the 3-tab-indented site, not the 2-tab one. Effect (no safety impact, but defeats the feature): enabling [runtime.userns] on a native-Linux pool left the value stranded at the zero (disabled) value, so every job ran UNMAPPED with no error and no log — the silent-no-op the fail-closed design is meant to avoid, and it would make on-metal validation on a Linux pool validate nothing. The missing field defaults OFF, the safe direction, so default-off integrity is unaffected. Also: collapse the double Userns.Resolved() call in the create log to one local, and add TestUsernsSnapshotOpts asserting the remapper labels are absent when disabled and carry the same base+size as the spec maps when enabled (guards a transposed/half-applied remap that unit-passes today).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the mechanism for issue #126 — running each runner container in a remapped user namespace so container uid 0 maps to a high, unprivileged host uid instead of real host root. This is the structural layer beneath the capability/seccomp/AppArmor containment: with it on, a successful container escape (a runc CVE, a kernel LPE, the dind bind TOCTOU) lands as an unprivileged host uid owning nothing, rather than as root on the VM.
Phase 1 (this PR) — the core, safe to land default-off
[runtime.userns] { enabled, base_uid, base_gid, size }, DEFAULT OFF.Resolved()supplies base1000000000/ size65536, so an enabled-but-unconfigured pool can never map container root back onto host uid 0 (which would be no remap).oci.WithUserNamespace(uid/gid maps)viausernsSpecOpts, Linux-only, appended with the other spec-hardening opts.client.WithRemapperLabelsonWithNewSnapshot, so the rootfs snapshot presents mapped ownership (idmapped mount where the overlay snapshotter supports it). Fail-closed: a host that can't honor the remap fails the snapshot prepare and the job errors, rather than silently running unmapped with container root == host root.Default-off ⇒ a no-op for every existing pool. Validation is by enabling it on one pool on real hardware, exactly as the issue prescribes.
Not in this PR — follow-up, gated behind the same flag, needs on-metal validation
createUnder/translateBindSourceinpkg/dind), socontainer:/-vjobs work under the remap;ContainerDataDirpath.Standard Linux-image jobs (runner pre-installed in the image, no runner-mount overlay, no dind) are covered by the snapshot remap alone — that's the intended first canary target.
Tests
usernsSpecOpts: mapping + user-namespace assertions, incl. a guard that an enabled-but-unconfigured config resolves to a real base (never a degenerate map onto uid 0).Resolved()defaults + the load-bearing default-off assertion (an accidental default-on would remap every job on upgrade).Validated: builds + vet clean on linux/windows/darwin (amd64+arm64); config tests pass; runtime test binary compiles. The Containment suite's
#126userns check is expected to flip PASS on a pool with[runtime.userns] enabled = truerunning a standard-image job.