了解用户名、组名、用户 ID (uid) 和组 ID (gid) 如何在容器内运行的进程和主机系统之间映射对于构建安全系统很重要。如果没有提供任何其他选项,容器中的进程将以 root 身份执行(除非在 Dockerfile 中提供了不同的 uid)。本文将解释这是如何工作的,如何正确授予权限并通过示例进行说明

uid/gid 安全分析

首先,让我们回顾一下 uids 和 gids 是如何实现的。linux 内核负责管理 uid 和 gid 空间,内核级系统调用用于确定是否应授予请求的权限。例如,当一个进程试图写入一个文件时,内核会检查创建该进程的 uid 和 gid,以确定它是否有足够的权限来修改该文件。这里不使用用户名,使用 uid。
在服务器上运行 Docker 容器时,仍然只有一个内核。容器化带来的很大一部分价值是所有这些独立的进程可以继续共享一个内核。这意味着即使在运行 Docker 容器的服务器上,整个 uids 和 gids 世界也由单个内核控制。
因此,您不能在不同的容器中拥有具有相同 uid 的不同用户。这是因为出现在常见 linux 工具中的用户名(和组名)不是内核的一部分,而是由外部工具(/etc/passwd、LDAP、Kerberos 等)管理。因此,您可能会看到不同的用户名,但对于相同的 uid/gid,您不能拥有不同的权限,即使在不同的容器中也是如此。乍一看这似乎很令人困惑,所以让我们用几个例子来说明它:

简单的 Docker 运行

我将首先以 docker 组中的普通用户 (elephdev) 身份登录服务器。这允许我在不使用 sudo 命令的情况下启动 docker 容器。然后,从容器的外部,让我们看看这个过程是如何出现的

elephdev@server:~$ docker run -d ubuntu:latest sleep infinity
92c57a8a4eda60678f049b906f99053cbe3bf68a7261f118e411dee173484d10
elephdev@server:~$ ps aux | grep sleep
root 15638 0.1 0.0 4380 808 ? Ss 19:49 0:00 sleep infinity

我从未输入过 sudo 并且我不是 root,但我执行的 sleep 命令以 root 用户身份启动并具有 root 权限。 我怎么知道它有root权限? 容器内的root == 容器外的root 吗? 是的,因为正如我提到的,有一个内核和一个共享的 uid 和 gid 池。 因为用户名在容器外显示为“root”,我可以肯定地知道容器内的进程是由一个 uid = 0 的用户启动的

定义用户的 Dockerfile

当我在 Dockerfile 中创建不同的用户并以该用户身份启动命令时会发生什么?为了简化这个例子,我没有在这里指定一个 gid,但同样的概念也适用于组 id

首先,我以用户“elephdev”的身份运行这些命令,其 uid 为 1001

elephdev@server:~$ echo $UID 
1001

Dockerfile

FROM ubuntu:latest 
RUN useradd -r -u 1001 -g appuser appuser 
USER appuser 
ENTRYPOINT [“sleep”, “infinity”]

构建并运行

elephdev@server:~$ docker build -t test .
Sending build context to Docker daemon 14.34 kB
Step 1/4 : FROM ubuntu:latest
 — -> f49eec89601e
Step 2/4 : RUN useradd -r -u 1001 appuser
 — -> Running in 8c4c0a442ace
 — -> 6a81547f335e
Removing intermediate container 8c4c0a442ace
Step 3/4 : USER appuser
 — -> Running in acd9e30b4aba
 — -> fc1b765e227f
Removing intermediate container acd9e30b4aba
Step 4/4 : ENTRYPOINT sleep infinity
 — -> Running in a5710a32a8ed
 — -> fd1e2ab0fb75
Removing intermediate container a5710a32a8ed
Successfully built fd1e2ab0fb75
elephdev@server:~$ docker run -d test
8ad0cd43592e6c4314775392fb3149015adc25deb22e5e5ea07203ff53038073
elephdev@server:~$ ps aux | grep sleep
elephdev 16507 0.3 0.0 4380 668 ? Ss 20:02 0:00 sleep infinity
elephdev@server:~$ docker exec -it 8ad0 /bin/bash
appuser@8ad0cd43592e:/$ ps aux | grep sleep
appuser 1 0.0 0.0 4380 668 ? Ss 20:02 0:00 sleep infinity

我构建了一个 Docker 映像,它有一个名为“appuser”的用户,该用户的定义 uid 为 1001。在我的测试服务器上,我使用的帐户名为“marc”,它的 uid 也为 1001。当我启动容器,sleep 命令以 appuser 身份执行,因为 Dockerfile 包含“USER appuser”行。但这并没有让它以 appuser 的身份运行,而是让它以 Docker 镜像知道为 appuser 的用户的 uid 运行。

当我检查在容器外运行的进程时,我看到它映射到用户“marc”,但在容器内它映射到用户“appuser”。这两个用户名都只是显示了他们的执行上下文知道映射到 1001 的用户名。

这不是非常重要。但重要的是要知道在容器内部,用户“appuser”正在从容器外部获取用户“marc”的权限和特权。向 linux 主机上的用户 marc 或 uid 1001 授予权限也会将这些权限授予容器内的 appuser。

如何控制容器的访问权限

另一种选择是运行 docker 容器并在运行时指定用户名或 uid,以及组名或 gid。

再次使用上面的初始示例。

elephdev@server:~$ docker run -d --user 1001 ubuntu:latest sleep infinity
84f436065c90ac5f59a2256e8a27237cf8d7849d18e39e5370c36f9554254e2b
elephdev@server$ ps aux | grep sleep
marc     17058 0.1 0.0 4380 664 ? Ss 21:23 0:00 sleep infinity

这里做了什么? 我创建了以 1001 用户身份启动的容器。 因此,当我执行 ps 或 top(或大多数监控工具)等命令时,进程会映射到“marc”用户。

当执行该容器时,您可以看到 1001 用户在 /etc/passwd 文件中没有条目,并显示为“我没有名字!” 在容器的 bash 提示符中。

elephdev@server:~$ docker exec -it 84f43 /bin/bash
I have no name!@84f436065c90:/$

注意,在创建容器时指定用户标志也会覆盖 Dockerfile 中的该值。 还记得我使用的 Dockerfile 的第二个示例,其中 uid 映射到本地主机上的不同用户名吗? 当我们在命令行上使用用户标志运行它以启动执行“sleep infinity”进程的容器时会发生什么?

elephdev@server:$ docker run -d test
489a236261a0620e287e434ed1b15503c844648544694e538264e69d534d0d65
elephdev@server:~$ ps aux | grep sleep
elephdev 17689 0.2 0.0 4380 680 ? Ss 21:28 0:00 sleep infinity
elephdev@server:~$ docker run --user 0 -d test
ac27849fcbce066bad37190b5bf6a46cf526f56d889af61e7a02c3726438fa7a
elephdev@server:~$ ps aux | grep sleep
elephdev  17689 0.0 0.0 4380 680 ? Ss 21:28 0:00 sleep infinity
elephdev  17783 0.3 0.0 4380 668 ? Ss 21:28 0:00 sleep infinity

在上面的最后一个例子中,你可以看到我最终有 2 个容器运行 sleep 进程,一个作为“marc”,一个作为“root”。 这是因为第二个命令通过在命令行上传递 --user 标志来更改 uid。

意味着什么

既然我们已经探讨了这一点,那么运行具有有限权限的容器的方法都利用主机的用户系统是有道理的:

1.如果容器内的进程正在执行已知的 uid,那么它可能就像限制对主机系统的访问一样简单,以便来自容器的 uid 具有有限的访问权限。

2.更好的解决方案是使用 --user 使用已知 uid 启动容器(您也可以使用用户名,但请记住,这只是从主机的用户名系统提供 uid 的一种更友好的方式),然后限制对 uid 的访问 在您决定容器运行的主机上。

3.由于 uid 和用户名(以及 gid 和组名)如何从容器映射到主机,指定容器化进程运行的用户可以使该进程看起来由容器内部和外部的不同用户拥有。

点赞(8)

评论列表 共有 0 评论

暂无评论

微信服务号

微信客服

淘宝店铺

support@elephdev.com

发表
评论
Go
顶部