在Docker容器中使用卷挂载的文件权限

3
我想让一个 docker 容器从主机文件系统中访问 letsencrypt 证书。 我不想以 root 用户身份运行 Docker 容器,而是要求使用具有特定访问权限的用户身份运行。 我也不想更改证书的权限。 我只需要让给定的用户能够在 docker 容器内读取证书。 该证书具有以下设置:
-rw-r----- 1 root cert-group

运行docker容器的用户属于证书组:
uid=113(myuser) gid=117(myuser) groups=117(myuser),999(cert-group),998(docker)

只要我们在主机上,这个工作就能完成 - 我可以使用用户“myuser”正常读取文件。
现在我想在装有证书的卷中在docker容器内进行此操作。 我已经做了多个测试用例,但都没有成功。
一个简单的docker-compose测试文件:
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"

这段代码输出了很多内容,但最重要的是:

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:

在这里,我假设"ping"是docker alpine内部的一个组,但是我得到了一些关于它如何与主机协作的混合信息。
根据这篇文章 https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf,我的理解是,有一个单一的内核处理所有权限(即主机),因此如果使用相同的uid和gid,则权限将继承自主机。然而,即使正在运行的用户是113:117,在主机上也属于组999,它仍然无法让我访问文件。
接下来,我发现了这篇文章https://medium.com/@nielssj/docker-volumes-and-file-system-permissions-772c1aee23ca,特别是这个项目引起了我的注意:
容器OS根据其自己的配置强制执行容器运行时中进行的所有操作的文件权限。例如,如果用户A在主机和容器中都存在,则将用户A添加到主机上的组B将不允许用户A写入容器内由组B拥有的目录,除非在容器内也创建组B并将用户A添加到其中。
这让我想到,可能需要一个自定义Dockerfile,将用户添加到docker中,并使用户成为999的一部分(正如之前所述,这被称为ping)。
FROM alpine:latest
RUN adduser -S --uid 113 -G ping myuser
USER myuser

运行这个命令会给我完全相同的结果,现在在passwd后面添加了myuser:

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

以下是我尝试过的一些事情。

另一个是将 /etc/passwd 和 /etc/group 与在其他博客中找到的卷同步。

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

这使其在容器内视觉上正确,但并不改变最终结果——仍然是权限被拒绝。非常感谢任何帮助或指引,因为我已经没有更多想法了。
3个回答

9
Docker容器不知道在主机上运行容器的用户的uid/gid。所有运行容器的请求都通过Docker套接字进行,然后传递到通常作为root运行的Docker引擎,这些API调用中没有传递任何uid/gid。 Docker引擎只是按照Dockerfile中指定的用户或作为容器创建命令的一部分(在这种情况下,来自docker-compose.yml)运行容器。
一旦进入容器,从uid/gid到名称的映射使用位于容器内的/etc/passwd和/etc/group文件完成。重要的是,在文件系统级别上,uid/gid值不会在容器和主机之间进行映射(除了用户命名空间,但如果正确实现,这只能使问题更加严重)。并且所有文件系统操作都基于uid/gid级别而不是名称进行。因此,当您执行主机卷挂载时,uid/gid直接传递。
在此处遇到的问题是如何告诉容器选择uid/gid来运行容器进程。通过指定user: "113:117",您已经告诉容器不仅指定进程的uid(113),还指定了gid(117)。这样做时,来自/etc/group的任何辅助组都不会分配给该用户。要分配这些辅助组,请仅指定uid,user: "113",然后将从容器内的/etc/passwd/etc/group文件查找组分配。例如:
user: "113"

很遗憾,docker在挂载卷之前就完成了组成员的查找,因此会出现以下情况。

首先,创建一个带有示例用户并分配到几个组的镜像:

$ 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 .
...

接下来,运行该镜像,将主机上的id与容器内的id进行比较:
$ 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.

糟糕,Docker 没有在 /etc/passwd 中找到入口,让我们试试在镜像中创建的 test 用户:

$ 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

这个方法可行,它从镜像中的/etc/group文件中分配组,而不是我们挂载的文件。我们还可以看到uid也有效:

$ 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

一旦我们指定gid,次要组将会被删除:

$ 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)

如果我们在不覆盖 /etc/passwd/etc/group 文件的情况下运行,我们可以看到正确的权限:

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

可能最好的选择是添加一个容器用户,使其组成员与主机的uid/gid值匹配。对于主机卷,我也通过一个基础镜像来解决这个问题,在容器内动态调整用户或组以与挂载在卷中的文件的uid/gid匹配。这是以root权限执行的,然后使用gosu将权限降回到用户级别。你可以在github上的sudo-bmitch/docker-base看到这个过程,具体是fix-perms脚本,我会将其作为入口点的一部分运行。
另外,请注意,挂载 /etc/passwd/etc/group 可能会破坏容器文件系统中其他文件的文件权限,并且这个用户可能有访问容器中不适当的权限(例如,你可能具有特殊访问ping命令的权限,从而可以修改文件或运行普通用户无法访问的ping命令)。这就是为什么我倾向于调整容器用户/组而不是完全替换这些文件的原因。

1

今天我也遇到了同样的问题,幸运的是,下面的解决方案帮助了我。

"在卷挂载中添加:Z"

参考:https://github.com/moby/moby/issues/41202

注意:不幸的是,这只是Centos的问题,我在Ubuntu上没有遇到任何问题。


追加 :Z 是什么意思?你能解释一下吗? - Matt
这对我也起作用了。文档链接 - VoNWooDSoN

0

其实你的解决方案并没有错。我也做了一些不同的地方。

这是我的 Dockerfile:

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

我的 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"

我的 '/tmp/test.txt' 被分配给 113:117。

在我看来,我认为问题出在你的 docker-compose.yml 文件中没有使用你的镜像。你应该删除 image: 并添加 build:


谢谢你的回答。是的,我有点把问题缩小到了主机上的“cert-group”与Docker中的“ping”在相同的GID下发生冲突。至少这是我最好的猜测。 - Lasse
是的,但你从docker-compose.yml文件中删除了user: "113:117"吗?我相当确定这不正确。在Dockerfile中定义用户并坚持使用它。 - Stefano
据我所知并且测试过,docker-compose文件中的用户输入将覆盖dockerfile中的USER。我已经发现设置user: "113:999"实际上可以使其工作,尽管这不是我想要的解决方案,因为113已经在组999中了。 - Lasse
你确定在运行其他测试之前已经移除了之前的容器(docker-compose down)吗? - Stefano
让我们在聊天中继续这个讨论 - Stefano
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接