Diving into Linux Namespaces: Understanding User Namespaces in Docker (Part-3)

Linux Mar 19, 2023

One of the main issues when running applications in containers is making sure that the containerized processes are isolated from the host system and other containers. Using user namespaces is one approach of accomplishing this. In this article, we'll look at how user namespaces work in Docker.
How do User Namespaces work? And how container technologies like docker use it to achieve isolation and enhance security ?

What are User Namespaces?

The Linux kernel's user namespaces feature enables processes to have their own segregated view of the system's user and group IDs. This implies that processes executing inside a user namespace can have their own unique set of user and group IDs that are distinct from those of the host system and other containers.
User Namespaces are useful for a variety of scenarios. One such example is in containerization technologies like Docker, where a process inside a container needs to run as a non-root user. By using User Namespaces, the process can run as a non-root user inside the container, while still having a different user ID than the same user outside the container.

Exploring a User Namespace

Let's explore how to create  a user namespace and execute a process inside of it. We'll use unshare command  to create new namespace and start processes inside it.
Let's explore it and get our hands dirty.

theperson@server:~/Blog$ id
uid=1000(theperson) gid=1000(theperson) groups=1000(theperson)
theperson@server:~/Blog$ unshare --user
nobody@server:~/Blog$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

when creating a new user namespace and running the id command to see the UID, GID, and groups of the current process. In the new namespace, the process belongs to the nobody user with a UID and GID of 65534, which a default user  in the system. When a user ID has no mapping inside the namespace, system calls that return user IDs return the value defined in the file /proc/sys/kernel/overflowuid. There is dynamic user ID mapping when a process needs to perform system-wide operations.

Let’s create a file and its ownership and let's check it from view of a process in the root USER namespace.

nobody@server:~/Blog$ touch test
nobody@server:~/Blog$ ls -la
total 8
drwxrwxr-x 2 nobody nogroup 4096 Mar 19 15:33 .
drwxr-xr-x 3 nobody nogroup 4096 Mar 19 15:30 ..
-rw-rw-r-- 1 nobody nogroup    0 Mar 19 15:33 test
nobody@server:~/Blog$ echo $BASHPID
113588
# in another terminal
theperson@server:~/Blog$ ls -la
total 8
drwxrwxr-x 2 theperson theperson 4096 Mar 19 15:33 .
drwxr-xr-x 3 theperson theperson 4096 Mar 19 15:30 ..
-rw-rw-r-- 1 theperson theperson    0 Mar 19 15:33 test
theperson@server:~/Blog$ ps -aux | grep bash
root      111598  0.0  0.0  11248  5480 pts/0    Ss+  12:53   0:03 -bash
root      113535  0.2  0.0  11248  5252 pts/1    Ss   15:30   0:00 -bash
root      113556  0.0  0.0  11248  5232 pts/2    Ss   15:30   0:00 -bash
thepers+  113575  0.0  0.0  11256  5108 pts/2    S    15:30   0:00 bash
thepers+  113588  0.0  0.0  11256  5528 pts/2    S+   15:30   0:00 -bash
thepers+  113616  0.2  0.0  11256  5516 pts/1    S    15:31   0:00 bash
thepers+  113727  0.0  0.0   9076  2300 pts/1    S+   15:33   0:00 grep --color=auto bash

In that scenario the shell process in the root user namespace can identify that the process (113588) running inside the USER namespace has an identical UID , which clarifies why the file created by nobody the new USER namespace appears to belong to the theperson user from  point of view of a process in the root USER namespace.

nobody@server:~/Blog$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody@server:~/Blog$ touch /test
touch: cannot touch '/test': Permission denied
nobody@server:~/Blog$ 

In the new user namespace, the process attempting to create a file in the root directory is denied permission because the directory contents are owned by the user owning the process inside the user namespace, and the process cannot modify them. The user (root) who owns these files is not being remapped, resulting in the process seeing nobody and nogroup. However, the process tries to modify the contents of the directory using its UID (1000) in the root user namespace, which is different from the UID of the files.

To overcome this, a mapping is required to map the UID and GID of a user inside a user namespace to another UID and GID in the root user namespace. With this mapping, the process can interact with the file system using the appropriate UID and GID in the root user namespace.

Now let's do the mapping for the shell process inside the user namespace And see how it will behave.

# In the root namespace
theperson@server:~/Blog$ echo "0 1000 65335" | sudo tee /proc/113588/uid_map
0 1000 65335
theperson@server:~/Blog$ echo "0 1000 65335" | sudo tee /proc/113588/gid_map
0 1000 65335
# In the user namespace shell 
nobody@server:~/Blog$ id
uid=0(root) gid=0(root) groups=0(root)
nobody@server:~/Blog$ touch test2
nobody@server:~/Blog$ ls -la
total 8
drwxrwxr-x 2 root root 4096 Mar 19 17:19 .
drwxr-xr-x 3 root root 4096 Mar 19 15:30 ..
-rw-rw-r-- 1 root root    0 Mar 19 15:33 test
-rw-rw-r-- 1 root root    0 Mar 19 17:19 test2
# In the root namespace
theperson@server:~/Blog$ ls -la
total 8
drwxrwxr-x 2 theperson theperson 4096 Mar 19 17:19 .
drwxr-xr-x 3 theperson theperson 4096 Mar 19 15:30 ..
-rw-rw-r-- 1 theperson theperson    0 Mar 19 15:33 test
-rw-rw-r-- 1 theperson theperson    0 Mar 19 17:19 test2

Using User Namespaces in Docker

Security is an important consideration when using Docker to build and deploy containerized applications. Like any software platform, Docker can be vulnerable to security risks. In this section, we'll explore how user namespaces can be used to enhance Docker security. Specifically, we'll look at how you can use the --user option and enable user namespaces in the Docker daemon.

The --user option in Docker can help to reduce the attack surface of containers by specifying a non-root user and group to run the container. Running containers with a non-root user and group can limit the potential impact of any security vulnerabilities in your containers. By default, Docker containers run as the root user, which could allow malicious actors to gain privileged access to the host system.

To use the --user option in Docker, you can specify a UID and GID like this.

theperson@server:/root$ docker run --user 1000:1000 alpine id
uid=1000 gid=1000 groups=1000

However, using the --user option may affect application functionality since some applications may require elevated privileges to function correctly. In such cases, you may need to modify your application or container configuration to allow for the necessary permissions. Here where the User namespaces can  be used in Docker to help increase container security. By default, Docker runs containers with the same user and group IDs as the host system. This means that if an attacker gains access to a container, they could potentially escalate their privileges to the host system. User namespaces can help prevent this by remapping the user and group IDs used inside the container to different IDs outside the container.

To enable user namespaces in Docker, let's add the following configuration to the /etc/docker/daemon.json file:

{
  "userns-remap": "default"
}

Once you enable user namespaces, you can specify a user namespace mapping when running a container. This mapping specifies the user and group IDs inside the container that will be mapped to the user and group IDs on the host system.

docker run -it alpine sleep 300
# in an other terminal
theperson@server:/root$ docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
14e751d02736   alpine    "sleep 300"   2 minutes ago   Up 2 minutes             admiring_northcutt
theperson@server:/root$ ps -aux | grep sleep
thepers+  119264  0.9  0.4 1413060 35532 pts/3   Sl+  21:03   0:01 docker run -it alpine sleep 300
165536    119382  0.3  0.0   1608     4 pts/0    Ss+  21:04   0:00 sleep 300
thepers+  119587  0.0  0.0   9076  2068 pts/4    S+   21:07   0:00 grep --color=auto sleep

In this scenario, we are running the alpine container with a user namespace mapping that maps the user and group IDs inside the container to the IDs of the user 165536 and the group 65536 on the host system.

conclusion

Understanding user namespaces in Docker is important for creating secure and scalable containerized applications. By utilizing Linux namespaces, Docker can isolate container user IDs and groups from the host system, which can decrease the likelihood of privilege escalation attacks.

References

Tags