Understanding how user names, group names, user IDs (uid), and group IDs (gid) map between the processes running in the container and the host system is important for building a secure system. If no other options are provided, the process in the container will execute as root (unless a different uid is provided in the Dockerfile). This article will explain how this works, how to grant permissions correctly and illustrate with examples

uid/gid security analysis

First, let us review how uids and gids are implemented. The linux kernel is responsible for managing the uid and gid spaces, and kernel-level system calls are used to determine whether the requested permissions should be granted. For example, when a process attempts to write to a file, the kernel checks the uid and gid that created the process to determine whether it has sufficient permissions to modify the file. The user name is not used here, uid is used.
When running a Docker container on a server, there is still only one core. A large part of the value brought by containerization is that all these independent processes can continue to share a core. This means that even on servers running Docker containers, the entire world of uids and gids is controlled by a single kernel.
Therefore, you cannot have different users with the same uid in different containers. This is because the user names (and group names) that appear in common linux tools are not part of the kernel, but are managed by external tools (/etc/passwd , LDAP, Kerberos, etc.). Therefore, you may see different user names, but for the same uid/gid, you cannot have different permissions, even in different containers. This may seem confusing at first glance, so let's illustrate it with a few examples:

Simple Docker operation

I will first log in to the server as a normal user (elephdev) in the docker group. This allows me to start the docker container without using the sudo command. Then, from the outside of the container, let's see how this process occurs

elephdev@server:~$ docker run -d ubuntu:latest sleep infinity
92c57a8a4eda60678f049b906f99053cbe3bf68a7261f118e411dee173484d10
elephdev@server:~$ ps aux | grep sleep
root 15638 0.1 0.0 4380 808? Ss 19:49 0:00 sleep infinity

I have never entered sudo and I am not root, but the sleep command I executed was started as the root user with root privileges. How do I know it has root privileges? Is the root inside the container == the root outside the container? Yes, because as I mentioned, there is a kernel and a shared pool of uid and gid. Because the user name is displayed as "root" outside the container, I can definitely know that the process in the container was started by a user with uid = 0

Define the user's Dockerfile

What happens when I create a different user in the Dockerfile and start the command as that user? To simplify this example, I did not specify a gid here, but the same concept applies to group ids

First, I run these commands as the user "elephdev" with a uid of 1001

elephdev@server:~$ echo $UID
1001

Dockerfile

FROM ubuntu:latest
RUN useradd -r -u 1001 -g appuser appuser
USER appuser
ENTRYPOINT ["sleep", "infinity"]

Build and run

elephdev@server:~$ docker build -t test.
Sending build context to Docker daemon 14.34 kB
Step 1/4: FROM ubuntu:latest
 — -> f49eec89601e
Step 2/4: RUN useradd -r -u 1001 appuser
 — -> Running in 8c4c0a442ace
 — -> 6a81547f335e
Removing intermediate container 8c4c0a442ace
Step 3/4: USER appuser
 — -> Running in acd9e30b4aba
 — -> fc1b765e227f
Removing intermediate container acd9e30b4aba
Step 4/4: ENTRYPOINT sleep infinity
 — -> Running in a5710a32a8ed
 — -> fd1e2ab0fb75
Removing intermediate container a5710a32a8ed
Successfully built fd1e2ab0fb75
elephdev@server:~$ docker run -d test
8ad0cd43592e6c4314775392fb3149015adc25deb22e5e5ea07203ff53038073
elephdev@server:~$ ps aux | grep sleep
elephdev 16507 0.3 0.0 4380 668? Ss 20:02 0:00 sleep infinity
elephdev@server:~$ docker exec -it 8ad0 /bin/bash
appuser@8ad0cd43592e:/$ ps aux | grep sleep
appuser 1 0.0 0.0 4380 668? Ss 20:02 0:00 sleep infinity

I built a Docker image with a user named "appuser" with a defined uid of 1001. On my test server, the account I used was named "marc" and its uid was also 1001. When I start the container, the sleep command is executed as appuser because the Dockerfile contains the "USER appuser" line. But this does not make it run as appuser, but makes it run with the uid of the user that the Docker image knows to be appuser.

When I check the process running outside the container, I see that it is mapped to the user "marc", but inside the container it is mapped to the user "appuser". Both of these usernames just show that their execution context knows the username mapped to 1001.

This is not very important. But it is important to know that inside the container, the user "appuser" is acquiring the permissions and privileges of the user "marc" from outside the container. Granting permissions to user marc or uid 1001 on the linux host will also grant these permissions to appuser in the container.

How to control access to containers

Another option is to run the docker container and specify the username or uid and group name or gid at runtime.

Use the initial example above again.

elephdev@server:~$ docker run -d --user 1001 ubuntu:latest sleep infinity
84f436065c90ac5f59a2256e8a27237cf8d7849d18e39e5370c36f9554254e2b
elephdev@server$ ps aux | grep sleep
marc 17058 0.1 0.0 4380 664? Ss 21:23 0:00 sleep infinity

What is done here? I created a container started as user 1001. Therefore, when I execute commands such as ps or top (or most monitoring tools), the process is mapped to the "marc" user.

When the container is executed, you can see that user 1001 has no entry in the /etc/passwd file and is displayed as "I have no name!" in the bash prompt of the container.

elephdev@server:~$ docker exec -it 84f43 /bin/bash
I have no name!@84f436065c90:/$

Note that specifying the user flag when creating the container will also override the value in the Dockerfile. Remember the second example of the Dockerfile I used, where uid is mapped to a different username on the local host? What happens when we run it on the command line with user flags to start the container that executes the "sleep infinity" process?

elephdev@server:$ docker run -d test
489a236261a0620e287e434ed1b15503c844648544694e538264e69d534d0d65
elephdev@server:~$ ps aux | grep sleep
elephdev 17689 0.2 0.0 4380 680? Ss 21:28 0:00 sleep infinity
elephdev@server:~$ docker run --user 0 -d test
ac27849fcbce066bad37190b5bf6a46cf526f56d889af61e7a02c3726438fa7a
elephdev@server:~$ ps aux | grep sleep
elephdev 17689 0.0 0.0 4380 680? Ss 21:28 0:00 sleep infinity
elephdev 17783 0.3 0.0 4380 668? Ss 21:28 0:00 sleep infinity

In the last example above, you can see that I ended up with 2 containers running sleep processes, one as "marc" and one as "root". This is because the second command changes the uid by passing the --user flag on the command line.

What does it mean

Now that we have discussed this, it makes sense that all methods of running a container with limited permissions use the host's user system:

  1. If the process in the container is executing a known uid, then it may be as simple as restricting access to the host system so that the uid from the container has limited access.

  2. A better solution is to use --user to start the container with a known uid (you can also use the username, but remember, this is just a more friendly way to provide the uid from the host's username system), Then restrict access to the uid on the host you decide to run the container on.

  3. Because of how the uid and user name (and gid and group name) are mapped from the container to the host, specifying the user under which the containerized process runs can make the process appear to be owned by different users inside and outside the container.

点赞(8)

评论列表 共有 0 评论

暂无评论

微信服务号

微信客服

淘宝店铺

support@elephdev.com

发表
评论
Go
顶部