带有卷挂载的 docker 容器中的文件权限

Las*_*sse 3 permissions docker docker-compose

我试图让 docker 容器从主机文件系统访问 LetsEncrypt 证书。

我不想以 root 身份运行 docker 容器,而是以具有非常特定访问权限的用户身份运行。我也不想更改证书的权限。我想要的只是给定用户能够读取 docker 容器内的证书。

该证书具有以下设置:

-rw-r----- 1 root cert-group
Run Code Online (Sandbox Code Playgroud)

将要运行 docker 容器的用户位于 cert-group 中:

uid=113(myuser) gid=117(myuser) groups=117(myuser),999(cert-group),998(docker)
Run Code Online (Sandbox Code Playgroud)

只要我们在主机上,这就可以工作 - 我能够按照用户“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"
Run Code Online (Sandbox Code Playgroud)

这输出很多,但最重要的是:

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:
Run Code Online (Sandbox Code Playgroud)

在这里,我假设“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,其中特别是这个要点引起了我的注意:

容器操作系统根据自己的配置对容器运行时中进行的所有操作强制执行文件权限。例如,如果主机和容器中都存在用户 A,则将用户 A 添加到主机上的组 B 将不允许用户 A 写入容器内组 B 拥有的目录,除非也在容器内创建了组 B并且将用户A添加到其中。

这让我想到,也许需要一个自定义 Dockerfile,将用户添加到 docker 中,并使用户成为 999 的一部分(如前所述,这称为 ping):

FROM alpine:latest
RUN adduser -S --uid 113 -G ping myuser
USER myuser
Run Code Online (Sandbox Code Playgroud)

运行此命令会得到完全相同的结果,但现在将 myuser 附加到 passwd 中:

test_1  | myuser:x:113:999:Linux User,,,:/home/myuser:/sbin/nologin
Run Code Online (Sandbox Code Playgroud)

这只是我尝试过的一些事情。

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

volumes:
  - /etc/passwd:/etc/passwd
  - /etc/group:/etc/group
Run Code Online (Sandbox Code Playgroud)

这使得它在容器内看起来是正确的,但它不会改变最终结果 - 权限仍然被拒绝。

任何正确方向的帮助或指示都将非常感激,因为我已经没有想法了。

BMi*_*tch 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"
Run Code Online (Sandbox Code Playgroud)

不幸的是,组成员身份的查找是由 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 .
...
Run Code Online (Sandbox Code Playgroud)

接下来,运行该映像,将主机上的 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.
Run Code Online (Sandbox Code Playgroud)

糟糕,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
Run Code Online (Sandbox Code Playgroud)

这有效,并从映像中的文件分配组/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
Run Code Online (Sandbox Code Playgroud)

一旦我们指定了 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)
Run Code Online (Sandbox Code Playgroud)

如果我们运行而不覆盖/etc/passwdand/etc/group文件,我们可以看到正确的权限:

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

可能最好的选择是添加一个容器用户,其组成员身份与主机的 uid/gid 值匹配。对于主机卷,我还使用基本映像解决了这个问题,该映像动态调整容器内的用户或组以匹配卷中安装的文件的 uid/gid。这是以 root 身份完成的,然后使用 gosu 将权限恢复给用户。您可以在 github 上的sudo-bmitch/docker-base上看到,特别是我将作为入口点的一部分运行的fix-perms 脚本

另外,请注意,挂载/etc/passwd/etc/group可能会破坏容器文件系统中其他文件的文件权限,并且该用户可能具有不适当的容器内部访问权限(例如,您可能具有对 ping 命令的特殊访问权限,该命令能够修改文件或运行普通用户无权访问的 ping 命令)。这就是为什么我倾向于调整容器用户/组而不是完全替换这些文件。