Repository navigation
Honor a container image's USER and WORKDIR - #1918
Davanum Srinivas (dims) wants to merge 6 commits into
Conversation
7321a72 to
4ab3b53
Compare
4ab3b53 to
58e3ad2
Compare
58e3ad2 to
9ed0a39
Compare
… note the rootfs root dependency of kagent-upstream#125 (agent-substrate#1918)
Maya Wang (mayawang)
left a comment
There was a problem hiding this comment.
The USER and WORKDIR resolution looks right to me, and moving to moby/sys/user covers Ben's gid point. My main ask is a live run before this merges, since it changes who real images run as. It also needs a rebase: internal/ocispec/parity_test.go conflicts with main.
… root dependency of kagent-upstream#125 (#94) docs(fork): the egress default's upstream exit is agent-substrate#1558; note the rootfs root dependency of kagent-upstream#125 (agent-substrate#1918)
9ed0a39 to
0ca0710
Compare
0ca0710 to
d41a2fc
Compare
|
Two gVisor issues from Preston Holmes (@ptone) that every image this change moves to non-root will hit: #2035 (the actor rootfs |
|
One case to plan for once this lands: images whose With this PR as it stands, those images would start the supervisor as the non-root user and fail. Could (Checked on |
|
Maya Wang (@mayawang) i will take up your offer, please open another issue. Let me rebase this and get it to green again. |
7f4bf05 to
fc7c1bd
Compare
This makes sense. For the moment I think we're blocked on the large PR to make layers match their image owners. They're security fixes IMO so still viable during code freeze, but we have a lot to work on. |
fc7c1bd to
54bfacc
Compare
|
Yeah. Commented more there |
e0bec5d to
063854a
Compare
rootfs and upper were 0700. On gVisor the container's / takes that mode, so a non-root process could not search its own root. 0755 is what containerd and every image root use; the 0700 actor directory above keeps other actors out. The micro-VM guest already sees 0755. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
atelet writes the OCI spec before the worker mounts the rootfs, so it cannot read the image's /etc/passwd from a mount. Image.ReadFile reads a file from the cached layers top-down, honoring recorded whiteouts and opaque dirs. Capped at 1 MiB. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Resolve USER as Docker does, against the image's own /etc/passwd and /etc/group: uid:gid as is, a bare uid with its login group (gid 0 without an entry), names looked up or Run/Restore fails, group memberships as supplementary groups. No USER means root. atelet resolves it into the worker's ContainerSpec. runsc restore checks Process.User, so a gVisor snapshot taken before this of an image that sets USER needs re-taking. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Start the process in the image's WORKDIR instead of "/", and create the directory in the actor's upper when the image lacks it. atelet resolves it into the worker's ContainerSpec. runsc restore checks Process.Cwd as well as Process.User, so gVisor snapshots from before this need re-taking. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Substrate makes no compatibility promise before v1.0.0, so a note about one release's snapshots is noise in the upgrade procedure. The API guide describes how the image's USER and WORKDIR apply. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
The actor images built from this repository write at the root of their filesystem and into the durable-dir volumes atelet creates as root. With the default base now nonroot and a container running as its image USER, they would start as uid 65532 and lose those writes, so the e2e demos break. These images stay on the root variant of the same distroless build. Signed-off-by: Davanum Srinivas <davanum@gmail.com>
063854a to
8b692a5
Compare
|
this will be part of #2392 - thanks for picking up my commit from here Anish Ramasekar (@aramase) |
…s kept (agent-substrate#2392) Containers ignored the image's `USER` and `WORKDIR` and ran as root in `/`, and atelet's unpack dropped the file owners image layers record. So an image that hands a directory to its user, such as jupyter's `/home/jovyan`, didn't work as built. - atelet applies each tar entry's uid/gid and keeps setuid, setgid and sticky bits. It adds `CHOWN`, `FOWNER` and `FSETID` to its `DAC_OVERRIDE`. The image cache layout moves to version 3. - The process runs as the image's `USER`, resolved as Docker does against the image's own `/etc/passwd` and `/etc/group`, and starts in its `WORKDIR` (default `/`). - The jupyter demo runs as jovyan and drops `DAC_OVERRIDE` and `--allow-root`. The `USER` and `WORKDIR` commits are @dims's from agent-substrate#1918, kept as written. This PR supersedes agent-substrate#1918. Fixes agent-substrate#2036 #### Breaking change Please add the `breaking-change` label. - After an upgrade or rollback, atelet refuses the existing image cache. Delete `/var/lib/ate/image-cache` on each node once no actor runs on it, as atelet's error says. After an upgrade, a node reboot also works. - A custom atelet securityContext needs `CHOWN`, `DAC_OVERRIDE`, `FOWNER` and `FSETID`. - Images with a `USER` stop running as root, and images with a `WORKDIR` stop starting in `/`. A non-root `USER` is expected to have no effective capabilities, so `securityContext.capabilities.add` no longer helps it. This is checked under runc and in the gVisor source, not yet inside a gVisor or microVM sandbox. - gVisor snapshots of such images don't restore, because `runsc restore` checks each container's user and working directory. Recreate those templates. MicroVM snapshots resume as they were. - Files an earlier run wrote as root into a durable dir stay root-owned. Tested: `go test -race` on atelet, imagecache, ocispec, tarutil, kata and the probe fixture, on macOS and on Linux as non-root, and imagecache with `-count=3 -shuffle=on`. The root-only imagecache and tarutil tests pass as root on a privileged Linux container. `hack/verify-all.sh` passes. The jupyter image serves `/api` under Docker as 1000:100 with every capability dropped. @dims ran the jupyter demo and a non-root node image as their users on kind, on both runtimes, before agent-substrate#1904 merged; on microVM uid 1000 couldn't bind :80 then. A new imagefs e2e starts an actor from a probe image with `USER nonroot:nonroot` and `WORKDIR /home/nonroot`. It checks uid and gid 65532, no supplementary group besides 65532, no effective capabilities, the working directory, and that the user can create a file there. CI runs it on gVisor and on microVM. At this head nothing ran in a gVisor or microVM sandbox yet: not the jupyter demo and not the imagefs e2e. - [ ] Tests pass - [x] Docs updated --------- Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com> Signed-off-by: Davanum Srinivas <davanum@gmail.com> Co-authored-by: Davanum Srinivas <davanum@gmail.com>
USERresolves as Docker does, against the image's own/etc/passwdand/etc/group:uid:gidas is, a bare uid with its login group (gid 0 without an entry), names looked up orRun/Restorefails, group memberships as supplementary groups. NoUSERmeans root. atelet resolves it into the worker'sContainerSpec; the pause container follows the same rule throughWorkloadSpec.pause_user. The process starts in the image'sWORKDIR(default/), created in the actor's upper when the image lacks it.Behavior change: images with a
USERstop running as root; images with aWORKDIRstop starting in/. On gVisor no snapshot from before this restores:runsc restorechecksProcess.UserandProcess.Cwdof every container, and the pause image setsUSER 65535. Micro-VM snapshots resume as they were.Merge after #2392 (image layer owners): without it a non-root process cannot write the files its image gives it. On the micro-VM class, images that serve on a privileged port also need #1904.
Live run on kind, both classes, main + #1906 + #2392 + this PR: a node image (USER 1000, WORKDIR /app, durable dir) and the jupyter demo run as their user, write to their workdir and durable dir, and resume on demand after a suspend. On micro-VM uid 1000 cannot bind port 80 (#1904); gVisor allows it.
The actor images this repository builds with ko (the counter, egress and fspersist demos, the e2e probe and test server, glutton) bind port 80 or write at the root of their filesystem, and the default base is nonroot since #3028.
.ko.yamlkeeps them on the root variant of the same distroless build: they write at the root of their filesystem and into the durable-dir volumes atelet creates as root.go test -raceon Linux for atelet, imagecache, ocispec, ateworker-gvisor and kata.