简体   繁体   中英

File permission in docker container with volume mount

I'm trying to let a docker container access a letsencrypt certificate from the host file system.

I do not want to run the docker container as root, but rather as a user with very specific access rights. Neither do I want to change the permissions of the certificate. All I want, is for the given user, to have access to read the certificate inside the docker container.

The certificate has the following setup:

-rw-r----- 1 root cert-group

The user who's going to run the docker container, is in the cert-group:

uid=113(myuser) gid=117(myuser) groups=117(myuser),999(cert-group),998(docker)

This works as long as we're on the host - I am able to read the file as expected with the user "myuser".

Now I want to do this within a docker container with the certificate mounted as a volume. I have done multiple test cases, but none with any luck.

A simple docker-compose file for testing:

version: '3.7'

services:

  test:
    image: alpine:latest
    volumes:
      - /etc/ssl/letsencrypt/cert.pem:/cert.pem:ro
    command: > 
      sh -c 'ls -l / && cat /etc/passwd && cat /etc/group && cat /cert.pem'
    user: "113:117"
    restart: "no"

This ouputs a lot, but most important is:

test_1  | -rw-r-----    1 root     ping          3998 Jul 15 09:51 cert.pem
test_1  | cat: can't open '/cert.pem': Permission denied
test_1  | ping:x:999:

Here I assume that "ping" is an internal group for docker alpine, however, im getting some mixed information about how this collaborates with the host.

From this article https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf my takeaway is, that there's a single kernel handling all permissions (the host) and therefore if the same uid and gid is used, the permissions would inherit from the host. However, even though that the running user is 113:117, which on the host is part of the group 999 it still doesnt give me access to read the file.

Next I found this article https://medium.com/@nielssj/docker-volumes-and-file-system-permissions-772c1aee23ca where especially this bullet point caught my attention:

The container OS enforces file permissions on all operations made in the container runtime according to its own configuration. For example, if a user A exists in both host and container, adding user A to group B on the host will not allow user A to write to a directory owned by group B inside the container unless group B is created inside the container as well and user A is added to it.

This made me think, that maybe a custom Dockerfile was needed, to add the user inside docker, and make the user part of 999 (which is known as ping as earlier stated):

FROM alpine:latest
RUN adduser -S --uid 113 -G ping myuser
USER myuser

Running this gives me the exact same result, now with myuser appended to passwd though:

test_1  | myuser:x:113:999:Linux User,,,:/home/myuser:/sbin/nologin

This is just a couple of things that I've tried.

Another is syncing /etc/passwd and /etc/group with volumes found in some other blog

volumes:
  - /etc/passwd:/etc/passwd
  - /etc/group:/etc/group

This makes it visually look correct inside the container, but it doesnt change the end result - still permission denied.

Any help or pointers in the right direction would be really appreciated since I'm running out of ideas.

Docker containers do not know the uid/gid of the user running the container on the host. All requests to run containers go through the docker socket, and then to the docker engine that is often running as root, and no uid/gid's are passed in those API calls. The docker engine is just running the container as the user specified in the Dockerfile or as part of the container create command (in this case, from the docker-compose.yml).

Once inside the container, the mapping from uid/gid to names is done with the /etc/passwd and /etc/group file that is inside the container. Importantly, at the filesystem level, uid/gid values are not being mapped between the container and the host (with the exception of user namespaces, but if implemented properly, that would only make this problem worse). And all filesystem operations happen at the uid/gid level, not based on names. So when you do a host volume mount, the uid/gid's are passed directly through.

The issue you are encountering here is how you are telling the container to pick the uid/gid to run the container processes. By specifying user: "113:117" you have told the container to not only specify the uid (113), but also the gid (117) of the process. When that's done, none of the secondary groups from /etc/group are assigned to the user. To get those secondary groups assigned, you want to only specify the uid, user: "113" , which will then lookup the group assignments from the /etc/passwd and /etc/group file inside the container. Eg:

user: "113"

Unfortunately, the lookup for group membership is done by docker before any volumes are mounted, so you have the following scenario.

First, create an image with an example user assigned to a few groups:

$ cat df.users 
FROM alpine:latest

RUN addgroup -g 4242 group1 \
 && addgroup -g 8888 group2 \
 && adduser  -u 1000 -D -H test \
 && addgroup test group1 \
 && addgroup test group2

$ docker build -t test-users -f df.users .
...

Next, run that image, comparing the id on the host to the id inside the container:

$ id
uid=1000(bmitch) gid=1000(bmitch) groups=1000(bmitch),24(cdrom),25(floppy),...

$ docker run -it --rm -u bmitch -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
docker: Error response from daemon: unable to find user bmitch: no matching entries in passwd file.

Woops, docker doesn't see the entry from /etc/passwd, lets try with the test user we created in the image:

$ docker run -it --rm -u test -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch) groups=4242,8888

That works, and assigns the groups from the /etc/group file in the image, not the one we mounted. We can also see that uid works too:

$ docker run -it --rm -u 1000 -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch) groups=4242,8888

As soon as we specify the gid, the secondary groups are gone:

$ docker run -it --rm -u 1000:1000 -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro test-users:latest id
uid=1000(bmitch) gid=1000(bmitch)

And if we run without overriding the /etc/passwd and /etc/group file, we can see the correct permissions:

$ docker run -it --rm -u test test-users:latest id
uid=1000(test) gid=1000(test) groups=4242(group1),8888(group2)

Likely the best option is to add a container user with the group membership matching the uid/gid values from the host. For host volumes, I've also solved this problem with a base image that dynamically adjusts the user or group inside the container to match the uid/gid of the file mounted in a volume. This is done as root, and then gosu is used to drop permissions back to the user. You can see that at sudo-bmitch/docker-base on github, specifically the fix-perms script that I would run as part of an entrypoint .

Also, be aware that mounting the /etc/passwd and /etc/group can break file permissions of other files within the container filesystem, and this user may have access inside that container that is not appropriate (eg you may have special access to the ping command that gives the ability to modify files or run ping commands that a normal user wouldn't have access to). This is why I tend to adjust the container user/group rather than completely replace these files.

Actually your solution is not wrong. I did the same with few differences.

This is my Dockerfile:

FROM alpine:latest
RUN addgroup -S cert-group -g 117 \
    && adduser -S --uid 113 -G cert-group myuser
USER myuser

And my docker-compose.yml:

version: '3.7'

services:

  test:
    build: 
      dockerfile: ./Dockerfile
      context: .
    command: >
      sh -c 'ls -l / && cat /etc/passwd && cat /etc/group && cat /cert.pem'
    volumes:
      - "/tmp/test.txt:/cert.pem:ro"
    restart: "no"

My '/tmp/test.txt' is assigned to 113:117.

IMHO, I think the problem in your docker-compose.yml that doesn't use your image. You should remove the image: and add build:

I have gone through the same issue today and luckily, the below solution helped me.

"Add :Z to your volumes mounts"

Reference: https://github.com/moby/moby/issues/41202

Note: Unfortunately It's issue with only Centos, I didn't face any problem with Ubuntu.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM