Docker Without Root Privileges
Join the DZone community and get the full member experience.
Join For FreeDocker as Root
Docker runs its containers as root. But does your workload really needs root permissions? The answer is rarely. Still, your containers, by default, continue to run as a root-user. This could have serious security concerns. A process that runs inside the container as root is in fact a process running as root on the host itself. This provides an opportunity for a malicious attempt to gain unrestricted access to the host itself.
You can check it by yourself, just use the following command on any image that you commonly use:
xxxxxxxxxx
$ kubectl run -i --tty hello-world --image=hello-world --restart=Never -- sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:10 sh
Clearly, as a best practice, we should avoid running containers as root. So, how do we fix this, let us see how we can run a container as non-root user.
Add a Non-Root User to Dockerfile
Create a user with only as many permissions as is required by the workload inside the container. You can create a user with RUN command in the Dockerfile of the container’s image itself.
xxxxxxxxxx
RUN groupadd --gid 5000 newuser \
&& useradd --home-dir /home/newuser --create-home --uid 5000 \
--gid 5000 --shell /bin/sh --skel /dev/null newuser
The above line of code creates a user, newuser
, together with a home and shell for the user. Now, simply add the user in your Dockerfile, like the following example:
xxxxxxxxxx
FROM ubuntu:18.04
COPY . /myapp
RUN make /myapp
...
USER newuser
CMD python /myapp/hello.py
From line 5 onward, every command is run as newuser
and not as root. Simple, isn’t it?
But, we do not always make use of only our custom images; we also use many third-party images, and we won't be able to inject an unprivileged user into them like above.
These third-party Docker images will run as root user by default, unless we do something about them. If you use an image from a not-so popular source, then it may even be embedded with a malicious command, which could compromise the security of your cluster.
Kubernetes pod security context and pod security policies come to our rescue.
You may also like: Securing Your Docker Containers.
Use Pod Security Context
You may use a Pod Security Context to restrict the execution of a Pod to a particular non-root user. To specify these security settings for a Pod, add a securityContext
field in Pod specification.
xxxxxxxxxx
apiVersion v1
kind Pod
metadata
name my-pod
spec
securityContext
runAsUser5000
runAsGroup5000
volumes
name my-vol
emptyDir
containers
name my-container
image hello-world
command"sh" "-c" "sleep 10 m"
volumeMounts
name my-vol
mountPath /data/hello
securityContext
allowPrivilegeEscalationfalse
In the above specification, runAsUser
specifies that any container inside the pod will run with userID 5000
only. This is the user that we have created specifically as an unprivileged user. The runAsGroup
specifies the group id of all processes. If we do not mention this, then the group ID will be root (0).
Now, you can create this pod and check the processes running inside the container:
xxxxxxxxxx
$ kubectl apply -f my-pod.yaml
$ kubectl exec -it my-pod – sh
ps
PID USER TIME COMMAND
1 5000 0:00 sleep 10 m
6 5000 0:00 sh
As you can see above, the PID 1 is being run as userID 5000 and not as root.
Use Kubernetes Pod Security Policy
Kubernetes Pod Security Policy defines the conditions that a pod must run with; otherwise, it will not be provisioned in the cluster. In other words, if these conditions are not met, Kubernetes will prevent the pod from running.
A sample PodSecurityPolicy is given below:
xxxxxxxxxx
apiVersion policy/v1beta1
kind PodSecurityPolicy
metadata
name my-psp
spec
privilegedfalse
#Required to prevent escalations to root.
allowPrivilegeEscalationfalse
allowedCapabilities
'*'
volumes
'nfs'
hostNetworktrue
hostPorts
min8000
max8000
hostIPCtrue
hostPIDtrue
runAsUser
#Require the container to run without root.
rule'MustRunAsNonRoot'
seLinux
rule'RunAsAny'
supplementalGroups
rule'RunAsAny'
fsGroup
rule'RunAsAny'
This security policy achieves the following:
- Restrict a container from running in privileged mode.
- Restrict a container that needs a root.
- Restrict a container that access esvolumes other than NFS volume.
- Only allow containers to access host port 100.
Activate policy:
xxxxxxxxxx
$ kubectl create -f my-psp.yaml
Check the policy:
xxxxxxxxxx
$ kubectl get psp
NAME PRIV RUNASUSER FSGROUP SELINUX VOLUMES
My-psp false MustRunAsNonRoot RunAsAny RunAsAny [nfs]
Now that the policy has been created, you can test it by trying to run a container with root privileges.
xxxxxxxxxx
$ kubectl run --image=my-root-container
The pod security policy will disallow it to run and give an error message:
xxxxxxxxxx
$ kubectl get pods
NAME READY STATUS
my-root-pod 0/1 container has runAsNonRoot and image will run as root
Conclusion
In this post, I have highlighted the inherent risk in running a Docker container under the default settings of a root user. I have also proposed multiple ways to overcome this risk.
- If you are running a custom image, then create a new unprivileged user and specify it in your Dockerfile.
- If you are using third party images then you can set security context at pod or container level.
- Yet another way is to create a pod security policy that will not allow any container to run with root privileges.
Further Reading
Opinions expressed by DZone contributors are their own.
Comments