了解uid和gid如何在Docker容器中工作

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

逐步分析uid/god安全性

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

简单的Docker运行

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

marc@server:~$ docker run -d ubuntu:latest sleep infinity92c57a8a4eda60678f049b906f99053cbe3bf68a7261f118e411dee173484d10marc@server:~$ ps aux | grep sleeproot 15638 0.1 0.0 4380 808 ? Ss 19:49 0:00 sleep infinity复制代码

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

具有已定义用户的Dockerfile

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

首先,我将这些命令作为用户“marc”运行,其 uid 为1001。

marc@server:~$ echo $UID1001复制代码

和 Dockerfile:

FROM ubuntu:latestRUN useradd -r -u 1001 -g appuser appuserUSER appuserENTRYPOINT [“sleep”, “infinity”]复制代码

让我们构建并运行它:

marc@server:~$ docker build -t test .Sending build context to Docker daemon 14.34 kBStep 1/4 : FROM ubuntu:latest — -> f49eec89601eStep 2/4 : RUN useradd -r -u 1001 appuser — -> Running in 8c4c0a442ace — -> 6a81547f335eRemoving intermediate container 8c4c0a442aceStep 3/4 : USER appuser — -> Running in acd9e30b4aba — -> fc1b765e227fRemoving intermediate container acd9e30b4abaStep 4/4 : ENTRYPOINT sleep infinity — -> Running in a5710a32a8ed — -> fd1e2ab0fb75Removing intermediate container a5710a32a8edSuccessfully built fd1e2ab0fb75marc@server:~$ docker run -d test8ad0cd43592e6c4314775392fb3149015adc25deb22e5e5ea07203ff53038073marc@server:~$ ps aux | grep sleepmarc 16507 0.3 0.0 4380 668 ? Ss 20:02 0:00 sleep infinitymarc@server:~$ docker exec -it 8ad0 /bin/bashappuser@8ad0cd43592e:/$ ps aux | grep sleepappuser 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 运行,它使它作为用户的 uid 运行,Docker 镜像知道appuser.
当我检查在容器外部运行的进程时,我看到它被映射到用户“marc”,但是在容器内部它被映射到用户“appuser”。这两个用户名只显示其执行上下文知道的用户名映射到1001。
这不是非常重要的。但重要的是要知道在容器内部,用户“appuser”从容器外获取用户“marc”的权限和特权。在linux主机上向用户 marc 或uid 1001授予权限也将授予容器内 appuser 的这些权限。

如何控制容器的访问权限

另一种选择是运行 docker 容器并指定用户名或 uid,以及运行时的组名或gid。
再次使用上面的初始示例。
marc@server:~$ docker run -d --user 1001 ubuntu:latest sleep infinity84f436065c90ac5f59a2256e8a27237cf8d7849d18e39e5370c36f9554254e2bmarc@server$ ps aux | grep sleepmarc     17058 0.1 0.0 4380 664 ? Ss 21:23 0:00 sleep infinity复制代码
我在这做什么 我创建了容器以作为1001用户启动。因此,当我执行诸如 ps 或top(或大多数监视工具)之类的命令时,该过程将映射到“marc”用户。
有趣的是,当我执行到该容器时,您可以看到1001用户在/ etc / passwd 文件中没有条目,并在容器的 bash 提示符中显示为“我没有名字!”。
marc@server:~$ docker exec -it 84f43 /bin/bashI have no name!@84f436065c90:/$复制代码

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

marc@server:$ docker run -d test489a236261a0620e287e434ed1b15503c844648544694e538264e69d534d0d65marc@server:~$ ps aux | grep sleepmarc     17689 0.2 0.0 4380 680 ? Ss 21:28 0:00 sleep infinitymarc@server:~$ docker run --user 0 -d testac27849fcbce066bad37190b5bf6a46cf526f56d889af61e7a02c3726438fa7amarc@server:~$ ps aux | grep sleepmarc     17689 0.0 0.0 4380 680 ? Ss 21:28 0:00 sleep infinityroot     17783 0.3 0.0 4380 668 ? Ss 21:28 0:00 sleep infinity复制代码

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

这意味着什么?

现在我们已经探讨了这一点,有意义的是,运行具有有限权限的容器的方法都利用来自主机的用户系统:

1、如果已知的 uid 表示容器内的进程正在执行,则可能就像限制对主机系统的访问一样简单,以便容器中的 uid 有有限的访问权限。

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

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

注意这些测试

我在 Ubuntu 16.04服务器上运行这些测试。虽然可以在 Docker for OSX 上执行相同的命令和测试,但您会看到不同的结果,因为 Docker for OSX 实际上是在基于 Alpine Linux 的虚拟机上执行 docker-engine,并且您执行的命令正在OSX 上执行。

- end -

作者:Marc Campbell

本文转载于外网,转载请标明出处




分类:
阅读
标签:
收藏成功!
已添加到「」, 点击更改