When docker mounts the disk, as many containers use root to run programs inside the container, it will cause the files generated in the mount belong to root:root. generally the user outside the container is not root, which will make file sharing or even reading logs unnecessary trouble. This article aims to solve the problem of generating root privileged files from the root without changing the container.

The files generated by docker often need to be accessed by sudo chmod o+rw *, which is troublesome, but at least you have sudo privileges. Not so lucky when you don’t have sudo privileges, and that’s when I found three actions to solve the problem.

The article is divided into three parts, explaining how mounts work, providing three solutions, and giving a demonstration of the solution. If you only want to get the recommended solution, you can skip directly to the section on using subusers in the solution demo.

File mounting principle

In short, docker’s file mounts have the same uid and gid inside and outside the container .

The easiest way to mount a disk, e.g. docker run -v $PWD:/data bash, is to mount the current directory to the /data directory inside the container. The file permissions can be viewed with ls -l.

There may be two cases of running programs with root and non-root users inside the container. 1) If you run the program with root inside the container, you can imagine that the resulting file belongs to 0:0, i.e. root:root. the file outside the container will also belong to 0:0, also root:root. the resulting file requires root privileges outside the container to operate. 2) If you run the program with non-root inside the container, you can imagine that the resulting file belongs to 0:0, i.e. root:root. 2) If you run the program with non-root inside the container, for example blog❌1000:1000::/home/blog:/bin/bash, the resulting file belongs to 1000:1000. the file outside the container will also belong to 1000:1000, and the user with uid 1000 outside the container may be frank, which is if you happen to be frank, you can read this file without any problem. But if you are david with uid 1001, and frank is a nuisance, you can say goodbye to this file. Even outside the container there is no user with uid 1000, then this file does not belong to anyone.

Three solution principles

Restricted open users

This solution is only available for some containers and is difficult to determine if it is feasible. For these easily configurable containers, this problem is easily solved by forcing the container to run with the user docker run -u blog bash.

We define the easily configurable permissions containers this way.

  1. all users in the container can access all the resources in the container that need to be accessed and run
  2. all files are generated by the running user when the container is running
  3. access by the running user to the resources on the mounted disk that need to be accessed and run

Meeting these requirements can make the job of configuring a slightly more complex container much more complicated or even impossible. If you have ever created a container, then you can certainly imagine. The first point can at least be accomplished by changing the o attribute of all files in bulk. The second and third points, if the program running on the container needs to bind the reserved port, it needs special various settings; if the program running on the container simply requires administrator privileges, we can’t change or even decompile the program in order to create a docker that meets the requirements.

And most of the time we use the containers are configured by others, we can not determine whether the containers configured by others belong to easy to configure permissions containers. This kind of container if forced to run the container user in addition to not guarantee the permissions to generate files, but also may make the container accidentally crash. But say no, the bug somehow disappeared?

Use subuser

This problem can be easily solved if the uid:gid of the files inside and outside the container can be different. There is indeed a scheme that allows all uid:gid inside a container to be mapped with certain rules, I recommend using this scheme. I will demonstrate this solution in the solution demo section of the article.

In the official documentation, the scheme itself is designed to address security issues, see here. It’s not once or twice that a container mount disk root privilege lifting has occurred, and this is considered an official solution.

However, this solution will have some restrictions.

  • May not use --pid=host or --network=host
  • You may not use a mounted disk that is not recognized or uses user mapping
  • If you use -docker run --privileged you must also use --userns=host

It’s not a big restriction, just keep an eye on it when using it.

osxfs

Interestingly enough, this issue does not exist in OSX.

When discussing it with friends, they have reported that it does not exist, so I can’t help but feel that poverty has limited my imagination. If you are using OSX or have grabbed an OSX from a friend like I did, you can try the following command.

1
2
3
4
5
6
7
8
mkdir data
docker run -it --rm \
    -v $PWD/data:/data \
    bash:5.0 \
    touch /data/testfile
ls -l data

# -rw-r--r-- 1 user  staff  0  0 0 0:00 testfile

Despite all the problems with the OSX version of docker, at least it doesn’t have this problem. This is all thanks to osxfs, which has a basic introduction in the official documentation .

One of the Ownership paragraphs mentions the solution, where the root inside the container is mapped to the user running docker outside the container, and the record of file owner changes inside the container is kept in com.docker.owner. There are some other specific details, TL;DR.

Initially, any containerized process that requests ownership metadata of an object is told that its uid and gid own the object. When any containerized process changes the ownership of a shared file system object, such as by using the chown command, the new ownership information is persisted in the com.docker.owner extended attribute of the object. Subsequent requests for ownership metadata return the previously set values. Ownership-based permissions are only enforced at the macOS file system level with all accessing processes behaving as the user running Docker. If the user does not have permission to read extended attributes on an object (such as when that object’s permissions are 0000 ), osxfs attempts to add an access control list (ACL) entry that allows the user to read and write extended attributes. If this attempt fails, the object appears to be owned by the process accessing it until the extended attribute is readable again.

So the content is stored here.

1
2
3
4
5
6
7
8
docker run -it --rm \
    -v $PWD/data:/data \
    bash:5.0 \
    chown nobody /data/testfile
ls -l@ data

# -rw-r--r--@ 1 user  staff  0  0 0 0:00 testfile
# 	com.docker.owner	9

So this solution can’t be applied to other systems, and docker on OSX in general is just a test.

Some other ideas

If you build the container using a specific user, and that user ends up using the container as well, you don’t have this problem. For example, if you build a container with a user uid of 1000, and the user uid of the container is also 1000, but such a container is basically not portable, but it is not a simple way to use it for your own use.

Another idea is whether there are users with less privileges than all users. Just like the root user can access all user files, if there is a user whose files can be accessed by all users, then using this user to create docker would solve the problem. But in practice, no such user can be found, not even nobody, only nobody can access nobody’s files.

Program Demo

Restricted open user

Force the current user outside the container to run the program inside the container.

1
2
3
4
5
docker run -it --rm \
    -u `id -u` \
    -v $PWD/data:/data \
    bash:5.0 \
    touch /data/solution1

You can see that the generated file is owned by the current user.

1
2
3
ls -l data

# -rw-r--r-- 1 blog blog 0 0 00:00 solution1

This solution is the simplest, but as mentioned in the principles section, for many containers, forcing the running user of the container does not guarantee permissions to generate files, and may also allow the container to crash unexpectedly.

Use subuser

This solution requires changing the running parameters of the docker daemon, and it is recommended to open a virtual machine when testing, as demonstrated here with an unconfigured Ubuntu 16.

First complete the preparation by installing docker and creating user blog as a demo user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# install docker
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh

# add user blog
useradd -d /home/blog -m -s /bin/bash -G sudo,docker blog
passwd blog

# switch to blog
su blog
cd ~ && mkdir data

If no subuser is set, the generated files will be owned by root.

1
2
3
4
5
6
7
docker run -it --rm \
    -v $PWD/data:/data \
    bash:5.0 \
    touch /data/blog_without_subuid
    
ls -l data
# -rw-r--r-- 1 root root 0 0 00:00 blog_without_subuid

The setting of subuid is placed in /etc/subuid, and the uid of the current user can be obtained by id -u. The setting of subgid is placed in /etc/subgid, and the exclusive group of the current user can be obtained by cat /etc/group | grep $USER. Assuming here that blog:blog corresponds to uid:gid of 1000:1000, the two files are set as follows. The subuser format is name:start:number, which means that the user with the name name generates a number of subusers starting from start. These two lines generate a series of users outside the container with uid 1000, 100000, 100001, 100002..., which corresponds to the users inside the container, whose uid is 0, 1, 2, 3.... ` . Obviously, the root inside the container is mapped to the current user outside the container, and the same is true for the subuser group.

1
2
3
4
5
6
7
# sudo vim /etc/subuid
blog:1000:1
blog:100000:65536

# sudo vim /etc/subgid
blog:1000:1
blog:100000:65536

The following is done by setting up docker’s configuration file so that docker knows it needs to start user mapping and then just restarting docker.

1
2
3
4
5
6
# sudo vim /etc/docker/daemon.json
{
    "userns-remap": "bloger"
}

# sudo service docker restart

At this step, the subuser setup is complete and you can try the results.

1
2
3
4
5
6
7
docker run -it --rm \
    -v $PWD/data:/data \
    bash:5.0 \
    touch /data/blog_with_subuid
    
ls -l data
# -rw-r--r-- 1 blog blog 0 0 00:00 blog_with_subuid

Although files attributed to root are generated inside the container using root, files outside the container are attributed to the current user who has set the mapping.