None of these are root vegetables

Non-privileged containers based on the scratch image

I’m on my way home from an exceptionally interesting Cloud Native London meetup, where one of the talks was by Michael Hausenblas and Nic Jackson on the benefits of running containers as non-root users. You can find a ton of info about the topic at the snappily-named canihaznonprivilegedcontainers.info.

Michael and Nic showed how you can add commands to your Dockerfile to add a user, and then switch to that user identity before running the executable with the USER command.

An intriguing question came up: if you build a container based on the scratch image, can you run as a non-privileged user? I’m a big fan of using scratch as the basis for running binary executables (such as you might compile from a Go program), so I wanted to find out. That’s what 30-minute train journeys home are for, right?

Multi-stage builds give us the power

Here’s a Dockerfile that gives us what we’re looking for.

FROM ubuntu:latest
RUN useradd -u 10001 scratchuser
FROM scratch
COPY dosomething /dosomething
COPY --from=0 /etc/passwd /etc/passwd
USER scratchuser
ENTRYPOINT ["/dosomething"]

Building from this Dockerfile starts FROM an Ubuntu base image, and creates a new user called scratchuser. See that second FROM command? That’s the start of the next stage in the multi-stage build, this time working from the scratch base image (which contains nothing).

Into that empty file system we copy a binary called dosomething — it’s a trivial Go app that simply sleeps for a while.

package main
import "time"
func main() {
time.Sleep(500 * time.Second)
}

We also copy over the /etc/passwd file from the first stage of the build into the new image. This has scratchuser in it.

This is all we need to be able to transition to scratchuser before starting the dosomething executable.

The results

After building this image we can run it, and then look for the running dosomething from the host machine.

root@vm-ubuntu:~# ps faux | grep dosomething
root 5360 0.0 0.0 14228 968 pts/9 S+ 22:30 0:00 \_ grep --color=auto dosomething
10001 5262 0.0 0.0 2376 700 ? Ssl 22:30 0:00 \_ /dosomething

The first line of the results is the grep command itself, and the second is the executable running inside the container. As you can see, it is running under the user ID 10001. That ID is listed as a number rather than a name because it isn’t defined in the host’s /etc/passwd file.

Other possibilities

I’m pretty sure you could simply create an appropriately formatted, minimal /etc/passwd file (it’s just a text file, after all). and copy it into the scratch image. You would probably want to also create a group, but hey, it was only a short train ride home!

Detecting containers running as root

If you want to find containers that are running as root, Nic Jackson has created cnitch. It is also a feature available in the ultra-powerful Aqua Security console.

Aqua Security detected that this Redis container is running as root

EDITS: 6 Sep 2017 updated to include detector tool information