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