容器安全之启用用户命名空间(user namespace)
20、容器安全之启用用户命名空间
20.1、说明
1、在 host namespace 中运行容器
因为用户的 uid 在整个节点上是共享的,容器里的 uid 与宿主机的 uid 可能会有冲突。
容器内的root用户就是宿主机的root用户,容器内uid=1000的用户就是宿主机uid=1000的用户。
docker的守护进程是root权限的。
2、在 user namespace 中运行容器
(1) 官方文档: https://docs.docker.com/engine/security/userns-remap/
(2) 在没有 user namespace 的前提下,容器里的 root 和宿主机上的 root 的 uid 是一样的,有可能会存在安全问题,通过 user namespace 的
处理,可以将容器里的 root 账号映射到宿主机上普通用户的子uid上。
防止来自容器内的特权升级攻击的最佳方法是将容器的应用程序配置为以非特权用户身份运行。对于进程必须以 root 容器内的用户身份运行的容器,您可以
将此用户重新映射到 Docker 主机上的权限较低的用户。映射的用户被分配了一系列 UID,这些 UID 在命名空间中作为普通 UID 从 0 到 65536 起作用,但
在主机本身上没有特权。
(3) docker是使用 --userns-remap 容器用户映射宿主机用户的方式来解决问题,具体的方法描述如下:
用户和组的映射由两个配置文件来控制,分别是/etc/subuid和/etc/subgid。
echo "dockremap:100000:65536" | tee /etc/subuid;
echo "dockremap:100000:65536" | tee /etc/subgid;
(4) 对于 subuid 的这一行表示宿主机用户 dockremap 的子 uid 段为 [100000,10000+65536-1]。也就是说,容器内的用户 uid 与宿主机上
dockremap 用户的子 uid 存在映射关系。这意味着 UID 100000 在容器内被映射为 UID 0( root),UID 100001 在容器内被映射为 UID 1 等
等,如果进程试图在命名空间(容器)之外提升权限,则该进程将作为主机上的非特权高数字 UID 运行,甚至不会映射到真实用户,这意味着该
进程在主机系统上根本没有特权。
[0-99999] 这个区间范围内的uid仍然保留给宿主机进行使用,docker的守护进程是root权限的。
宿主机用户 dockremap 的容器子用户 id(subuid),只能在 [100000-165535] 之间进行分配。
启动容器A,容器用户 root(uid=0) 对应的宿主机用户是 uid=100000。
启动容器B,容器用户 root(uid=0) 对应的宿主机用户是 uid=100000。
(5) 先决条件
启用 userns-remap 有效地屏蔽现有的镜像和容器层,以及 /var/lib/docker/,这是因为 Docker 需要调整这些资源的所有权,并将它们实际存储
在 /var/lib/docker/,最好在新的 Docker 安装而不是现有的 Docker 安装上启用此功能。同样,如果禁用,则 userns-remap 无法访问启用时创
建的任何资源。
(6) 禁用容器的命名空间重新映射
如果您在守护程序上启用用户命名空间,则所有容器都默认启用用户命名空间。在某些情况下,例如特权容器,您可能需要禁用特定容器的用户名称空间。有关其中一些限制,请参阅 用户命名空间已知限制。
要为特定容器禁用用户名称空间,请将 --userns=host 标志添加到 docker container create、docker container run 或 docker container exec 命令。
使用此标志时有一个副作用:
不会为该容器启用用户重新映射,但是,因为只读(镜像)层在容器之间共享,容器文件系统的所有权仍将被重新映射。
这意味着整个容器文件系统将属于 --userns-remap 守护进程配置中指定的用户子ID。这可能会导致容器内的程序出现意外行为。例如 sudo(检查其二进制文件是否属于 user 0)或带有 setuid 标志的二
进制文件。
(7) 用户命名空间已知限制
以下标准 Docker 功能与运行启用了用户命名空间的 Docker 守护程序不兼容:
与主机共享 PID 或 NET 名称空间(--pid=host或--network=host)。
不知道或不能使用守护进程用户映射的外部(卷或存储)驱动程序。
在不指定的情况下使用 --privileged 模式标志。docker run--userns=host
用户命名空间是一项高级功能,需要与其他功能协调。例如,如果卷是从主机安装的,则文件所有权必须预先安排,需要对卷内容进行读取或写入访问。
虽然用户命名空间容器进程内的 root 用户拥有容器内超级用户的许多预期特权,但 Linux 内核会根据内部知识强加限制,即这是一个用户命名空间进程。一个值得注意的限制是无法使用该 mknod 命令。当
用户运行时,在容器内创建设备的权限被拒绝 root。
20.2、开启容器 user namespace 守护进程
1、说明
docker 容器是一种特殊的进程,namespace 是一种隔离技术,docker 就是使用隔离技术开启特定的 namespace 创建出一些特殊的进程(容器)。
默认情况下,docker 不会开启 user namespace,所以说容器没有对 user namespace 进行隔离。
2、如何在 docker 中开启用户隔离功能
建议在新安装好 docker 之后就开启用户命名空间的隔离功能。
https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.57e31b11J91EqX
(1) 在 /etc/docker/daemon.json 中加入如下配置,如何没有 daemon.json 这个文件,需要自己创建。
# vim /etc/docker/daemon.json
{
"userns-remap": "default"
}
(2) 重启docker
# systemctl restart docker
(3) 验证用户隔离功能开启成功
1) 首先 docker 创建了一个新的用户
# id dockremap
uid=997(dockremap) gid=993(dockremap) groups=993(dockremap)
2) 查看用户对应的子ID
# cat /etc/subuid
dockremap:100000:65536
# cat /etc/subgid
dockremap:100000:65536
UID 从 100000 开始,可以使用的 UID 共有 65535 个,即使用范围是 100000 - (100000+65536-1)。
GID 同 UID 计算方式一致。
3) 在 /var/lib/docker 目录下会创建一个以起始 UID 和 GID 命名的文件夹
100000 是 dockremap 用户映射出来的 ID,这个文件包含了 docker 创建容器的所有的内容,并且对某些目录进行了权限限制。
3 创建容器
开启 user namespace 后,容器中的 user ID 会产生什么变化呢,与宿主机中的 ID 又是如何对应的呢。
注意:
需要增加非特权用户最多可以创建用户命名空间限制的内核参数,默认 user.max_user_namespaces = 0,否则创建容器时会报下面的错误。
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: can't get final child's PID from pipe: EOF: unknown.
# cat >> /etc/sysctl.conf << EOF
user.max_user_namespaces = 15000
EOF
# sysctl -p
# docker run -p 8060:80 -d --name test1 ubuntu sleep infinity
100000 是用户 dockremap 的一个从属 ID,在主机上是一个普通的用户,接下来我们进入到容器中。
# docker exec -it test1 bash
可以看到容器中的 sleep 进程的 user 是 root,即在容器中进程是以 root 用户启动的。
虽然是以 root 启动的,但是容器内的 root 用户是通过主机上的 ID 为 100000 映射过来的,实际上它只有有限的权限,但是仍然以 root 用户在容器内运行。
4 对 volume 的访问权限
# docker run -d -v /root/test2:/root/test2 --name test2 ubuntu sleep infinity
# docker exec -it test2 bash
经测试可以看到非 dockremap 用户子 ID 权限的文件挂载到容器中,对应文件的所有权(ownership)变成了 nobody、nogroup,容器中的 root 用户有读的权限,但是没有写入的权限。
5 应用测试
# docker run -d -v /root/data/rabbitmq:/var/lib/rabbitmq --name test3 rabbitmq:3.7.26
6 屏蔽用户空间隔离功能
在 daemon.json 中配置 user-remap 是默认为所有的容器开启用户隔离的,如果想为某个容器 disable 这个功能,如下。
# docker run -d -v /root/test4:/root/test4 --userns=host --name test4 ubuntu sleep infinity
可以看到此时进程又归 root 所有了,挂载的卷的所有权也是 root。