The threat is here that a microservice is being offered to run under root
in the container. If the service contains a weakness the attacker has full privileges within the container. While there's still some default protection left (Linux capabilities, either AppArmor or SELinux profiles) it removes one layer of protection. This extra layer broadens the attack surface. It also violates the least privilege principle [1] and from the OWASP perspective is an insecure default.
For privileged containers (--privileged
) a breakout from the microservice into the container is almost comparable to run without any container. Privileged containers endanger your whole host and all other containers.
It is important to run your microservice with the least privilege possible.
First of all: Never use the --privileged
flag. It gives all so-called capabilities (see D04) to the container and it can access host devices (/dev
) including disks, and also has access to the /sys
and /proc
filesystem. And with a little work the container can even load kernel modules on the host [2]. The good thing is that containers are per default unprivileged. You would have to configure them explicitly to run privileged.
However still running your microservice under a different user as root requires configuration. You need to configure your mini distribution of your container to both contain a user (and maybe a group) and your service needs to make use of this user and group.
Basically there are two choices.
In a simple container scenario if you build your container you have to add RUN useradd <username>
or RUN adduser <username>
with the appropriate parameters -- respectively the same applies for group IDs. Then, before you start the microservice, the USER <username>
[3] switches to this user. Please note that a standard web server wants to use a port like 80 or 443. Configuring a user doesn't let you bind the server on any port below 1024. There's no need at all to bind to a low port for any service. You should then configure a higher port and map this port accordingly with the expose command [4]. If a binary needs root for other reasons you can grant them the capabitlity only using setcap
instead of full root privileges [5].
The second choice would be using Linux user namespaces. Namespaces are a general means to provide to a container a different (faked) view of Linux kernel resources. There are different resources available like User, Network, PID, IPC, see namespaces(7)
. In the case of user namespaces a container could be provided with a relative perspective of a standard root user whereas the host kernel maps this to a different user ID. More, see [6], cgroup_namespaces(7)
and user_namespaces(7)
.
User namespace does come with some limitations [7]. If you run user namespacing you e.g. can't share network/pid namespace with the host --pid=host
or --network=host
. Also, all your containers on a host will be defaulted to it, unless you explicitly configure this differently per container.
In any case use user IDs which haven't been taken yet. If you e.g. run a service in a container which maps outside the container to a systemd
user, this is not necessarily better.
Your mileage may vary if you're using an orchestration tool. In an orchestrated environment make sure that you have a proper pod security policy.
Depending on how you start your containers the first place is to have a look into the configuration / build file of your container whether it contains a user.
Have a look in the process list of the host, or use docker top
or docker inspect
.
-
ps auxwf
-
docker top <containerID>
orfor d in $(docker ps -q); do docker top $d; done
-
Determine the value of the key
Config/User
indocker inspect <containerID>
. For all running containers:docker inspect $(docker ps -q) --format='{{.Config.User}}'
The files /etc/subuid
and /etc/subgid
do the UID mapping for all containers. If they don't exist and /var/lib/docker/
doesn't contain any other entries owned by root:root
you're not using any UID remapping. On the other hand if those files exist and there are files in that directory you still need to check whether your docker daemon was started with --userns-remap
or the config file /etc/docker/daemon.json
was used.
- [1] OWASP: Security by Design Principles
- [3] Docker Docs: USER command
- [4] Docker Docs: EXPOSE command
- [5] Rasene's blog: https://raesene.github.io/blog/2017/07/23/network-tools-in-nonroot-docker-images/
- [6] Docker Docs: Isolate containers with a user namespace
- [7] Docker Docs: User namespace known limitations