Running Docker Containers with Non-root Users or Random User IDs

February 12, 2017

 

By default, Docker containers run as root. Using root is dangerous and it may not be available in all environments. “Best practices for writing Dockerfiles” recommend that “…If a service can run without privileges, use USER to change to a non-root user”.

There is a twist to this – for better security, some aPaaS (Application Platform-as-a-Service) like OpenShift use by default a user with random UID when running an image.

That leads us to the question – how can one build more secure Docker images so the containers can run as a concrete non-root user and with a random non-root one?

What got us looking into this was an issue with the previous API Simulator Docker images when running in containers the out-of-the-box API simulation examples. Creating log files for the API simulation examples was failing due to “Permission denied” error.

API Simulator Docker Image 0.5.0

Let’s examine the API Simulator Dockerfile for the image tagged 0.5.0 to understand the way we can make it happen:

# API Simulator Dockerfile
FROM openjdk:8u111-jre-alpine

MAINTAINER https://wp.apisimulator.io/

ENV APISIMULATOR_VERSION 0.5.0

# Download and install API Simulator
RUN apk update \
    && apk add ca-certificates wget \
    && update-ca-certificates \
    && wget -P . https://wp.apisimulator.io/downloads/apisimulator-http-$APISIMULATOR_VERSION-distro.zip \
    && unzip apisimulator-http-$APISIMULATOR_VERSION-distro.zip -d . \
    && rm -f apisimulator-http-$APISIMULATOR_VERSION-distro.zip \
    && addgroup -S -g 10101 apisimulator \
    && adduser -S -D -u 10101 -s /sbin/nologin -h /apisimulator -g "API Simulator User" -G apisimulator apisimulator \
    && chown -R apisimulator:root /apisimulator \
    && chmod -R 0775 /apisimulator

EXPOSE 6090/tcp

WORKDIR /apisimulator/apisimulator-http-$APISIMULATOR_VERSION

ENV PATH /apisimulator/apisimulator-http-$APISIMULATOR_VERSION/bin:$PATH

USER 10101

# No ENTRYPOINT or CMD to allow to run API Simulator or API Recorder from the same image
# @END

 

The key commands are these:

addgroup -S -g 10101 apisimulator

This adds a new system group (-S) with gid of 10101 and named “apisimulator”.

adduser -S -D -u 10101 -s /sbin/nologin -h /apisimulator -g "API Simulator User" -G apisimulator apisimulator

This adds a new system user (-S) without assigning a password (-D); the UID (-u) for the new user is 10101, login shell (-s) is “/sbin/nologin”; home directory is “/apisimulator”; the user belongs to the “apisimulator” group, and “apisimulator” is user’s name.

Notice that ‘addgroup’, ‘adduser’ and their options are specific to Alpine Linux. Check your Linux distribution for the equivalent commands and options.

chown -R apisimulator:root /apisimulator

This recursively changes the owner and group of the “/apisimulator” directory and files to the apisimulator user and root group, respectively.

chmod -R 0775 /apisimulator

This recursively changes the mode of each file and directory.
The command effectively sets read, write, and execute to the owner (apisimulator) and the group (root group), and read and execute to all other users given that:
7 = read + write + execute
5 = read + execute

USER 10101

The USER instruction sets the user UID to use when running the image to that of the newly created “apisimulator”.

 

Pull down the API Simulator Docker image 0.5.0 if you want to exercise the commands that follow:

sudo docker pull apimastery/apisimulator:0.5.0

 

Use Case #1 – Running API Simulator Container Using the Non-root User Set in the Image

We will simply run the image without specifying any user (no -u argument):

sudo docker run --rm -d --name apisimulator -p 6090:6090 -t apimastery/apisimulator:0.5.0 startup examples/apisims/first_steps

Send the following HTTP request to check that API Simulator is working:

curl -v "http://localhost:6090/api/geolocation/json?address=100+Pineapple+Parkway,+Alta+Vista,+CA"

The output should display HTTP 200 response with ‘”status” : “OK”‘ as one of the elements in the body.

Now that we are running API Simulator in a container without explicitly specifying a user, let’s do some poking around. To simplify the commands we will be running next, let’s assign the container ID to an environment variable:

export APISIM_CONTAINER_ID=`sudo docker ps -l -q`

You can compare the APISIM_CONTAINER_ID value with the output from `sudo docker ps`.

Let’s see under what user is the container running:

sudo docker exec $APISIM_CONTAINER_ID whoami
apisimulator

Execute this to find out what is the UID of the apisimulator user:

sudo docker exec $APISIM_CONTAINER_ID cat /etc/passwd | grep apisimulator
apisimulator:x:10101:10101:API Simulator User:/apisimulator:/sbin/nologin

The output shows that 10101 is the UID and 10101 is the group ID as set in the Docker image.

Looking at the simulation’s log file we see the apisimulator user and the apisimulator group as the user and group owners, respectively:

sudo docker exec $APISIM_CONTAINER_ID ls -l examples/apisims/first_steps/logs
-rw-r--r-- 1 apisimul apisimul      1723 Feb 12 18:25 apisimulator.log

The permissions on the file are such that the apisimulator user has read and write permissions; the apisimulator group and anyone else have read access.

The following command will show owners’ numeric UID (10101) and GID (10101) instead of the names:

sudo docker exec $APISIM_CONTAINER_ID ls -ln examples/apisims/first_steps/logs/
-rw-r--r-- 1 10101    10101         1723 Feb 12 18:25 apisimulator.log

Let’s peek inside the processes running inside the container (press q followed by Enter to exit):

sudo docker exec -i $APISIM_CONTAINER_ID top
...
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    8     1 apisimul S    1507m 308%   0   0% {java} /usr/lib/jvm/java-1.8-openj
    1     0 apisimul S     1520   0%   0   0% {apisimulator.sh} /bin/sh /apisimu
   40     0 apisimul R     1516   0%   0   0% top

The USER for all processes is apisimulator.

As the last step, stop the running container:

sudo docker stop $APISIM_CONTAINER_ID

 

Use Case #2 – Running API Simulator Container Using Random Non-root User

To have good reference point for this case, we will not exactly use a randomly generated non-root UID. We will use UID 202020 but we could have as well used any other UID that is different from a user already in use by the container and the UID the image sets (i.e. 10101).

We will execute the same commands as in Case #1 and compare the results. This will demonstrate that the API Simulator Docker image can run with the user set in the image as well as a random (non-root) user.

Let’s run the image specifying fake user (-u argument) with uid 202020:

sudo docker run --rm -d --name apisimulator -u 202020 -p 6090:6090 -t apimastery/apisimulator:0.5.0 startup examples/apisims/first_steps

Send the following HTTP request to check that API Simulator is working:

curl -v "http://localhost:6090/api/geolocation/json?address=100+Pineapple+Parkway,+Alta+Vista,+CA"

The output should display HTTP 200 response with ‘”status” : “OK”‘ as one of the elements in the body.

Now that we are running API Simulator in a container after explicitly specifying a user, let’s do some poking around.

To simplify the commands we will be running next, let’s assign the container ID to an environment variable:

export APISIM_CONTAINER_ID=`sudo docker ps -l -q`

You can compare the APISIM_CONTAINER_ID value with the output from `sudo docker ps`.

Let’s see under what user is the container running:

sudo docker exec $APISIM_CONTAINER_ID whoami
whoami: unknown uid 202020

Execute this to find out what is the UID of the apisimulator user returns nothing:

sudo docker exec $APISIM_CONTAINER_ID cat /etc/passwd | grep 202020

The following command reveals an important fact – user with uid 202020 belongs to the root group:

sudo docker exec $APISIM_CONTAINER_ID id
uid=202020 gid=0(root)

What that means is that user with uid 202020 gets the same permissions as any other user that belongs to the root group. In case of API Simulator, that is read, write, and execute for all files and directories in and under “/apisimulator”.

Not surprisingly then, checking out the simulation’s log file shows user 202020 and the root group as the user and group owners, respectively:

sudo docker exec $APISIM_CONTAINER_ID ls -l examples/apisims/first_steps/logs
-rw-r--r-- 1 202020 root 1723 Feb 12 20:16 apisimulator.log

The following command will show owners’ numeric UIDs (202020 ) and GIDs (0) instead of names:

sudo docker exec $APISIM_CONTAINER_ID ls -ln examples/apisims/first_steps/logs/
-rw-r--r-- 1 202020 0 1723 Feb 12 20:16 apisimulator.log

Let’s peek inside the processes running inside the container (press q followed by Enter to exit):

sudo docker exec -i $APISIM_CONTAINER_ID top
...
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    8     1 202020   S    1499m 307%   0   0% {java} /usr/lib/jvm/java-1.8-openj
    1     0 202020   S     1520   0%   0   0% {apisimulator.sh} /bin/sh /apisimu
   45     0 202020   R     1516   0%   0   0% top

As expected, 202020 is the USER for all processes.

As the last step, stop the running container:

sudo docker stop $APISIM_CONTAINER_ID

 

Credits

A good part of our solution was influenced by Graham Dumpleton’s blog article “Random user IDs when running Docker containers”.

 

Conclusion

Regarded as a best practice, running Docker containers as non-root users provides better security for everyone.

We ran two Use Cases demonstrating that the API Simulator Docker image can run with the non-root user set in the image as well as with a random (non-root) user provided at container startup time.

 


We invite you to learn more about API Simulator, download and install it, and run the examples. Why not even create API simulations to help your own testing and development?

Let us know what you think at [feedback at APISimulator.com]. Many thanks for your interest and support!

 

Happy Simulations,
The API Simulator Team