容器快了,却不安全了,Rootless 安排上
在以 root 用户身份运行 Docker 会带来一些潜在的危害和安全风险,这些风险包括:
-
1. 容器逃逸:如果一个容器以 root 权限运行,并且它包含了漏洞或者被攻击者滥用,那么攻击者可能会成功逃出容器,并在宿主系统上执行恶意操作。这会导致宿主系统的安全性受到威胁。
-
2. 特权升级:在以 root 用户身份运行 Docker 的情况下,容器内的进程可能会尝试特权升级,获取宿主系统的 root 权限。这可能会导致严重的安全问题,因为攻击者可能会利用这些权限来控制宿主系统。
-
3. 文件系统访问:以 root 用户身份运行的容器可以访问宿主系统上的文件系统,这可能会导致机密文件的泄漏或文件的损坏。
-
4. 网络权限:容器以 root 权限运行时,可能会滥用网络权限,例如进行端口扫描、DDoS 攻击等恶意行为。
为了减少这些风险,推荐采取以下做法:
-
• 以非root用户身份运行容器:最佳实践是在容器中以非root用户的身份运行应用程序。这可以通过在容器中指定普通用户来实现,并避免使用
USER
指令将容器进程切换到 root 用户。 -
• 限制容器的权限:使用 Docker 的安全配置选项,如
--security-opt
,可以限制容器的能力,例如禁止容器访问宿主系统的特定目录、文件和设备。 -
• 更新和监控容器:定期更新容器的基础镜像和应用程序,以确保安全漏洞得到修复。同时,使用容器监控和审计工具来检测不寻常的活动。
-
• 限制容器能力:使用 Docker 的能力(capabilities)设置来限制容器的权限,仅提供所需的最小权限来运行应用程序。
什么东西看似Rootless ,实则不然
-
•
docker run --user foo
:它允许你以非 root 身份在容器中执行进程。值得注意的是,你无法执行包安装等特权活动。runc、containerd 等仍以 root 身份运行。 -
•
usermod -aG docker foo
:允许非root用户连接到docker套接字。相当于允许用户以 root 身份运行。 -
•
sudo docker
和chmod +s dockerd
: 无需解释 -
•
dockerd --userns-remap
:它允许你以非 root 身份运行容器。runc、containerd 等仍然以 root 身份运行。
Docker Rootless 基本概念
Docker Rootless 是一种在非特权模式下运行 Docker 的方式,允许以非root用户身份来管理 Docker 守护进程和容器,以降低潜在的安全漏洞风险。在这种模式下,即使在 Docker 安装期间,也无需使用root权限。这有助于提高容器的安全性,因为以非特权用户身份运行容器可以限制容器内部的特权操作。对于特权模式的 Docker 容器,攻击者可能会利用宿主机文件读写权限等漏洞来逃逸,因此非特权模式更为安全。
同时,在 Docker 中,容器可以选择是否以特权模式运行,通过设置 --privileged=false
可以将容器切换为非特权模式。总的来说,Docker Rootless 模式提供了一种更加安全的方式来运行 Docker 容器,降低了潜在的安全风险,特别是在多租户环境中或需要更严格的容器隔离时,这种模式非常有用。
Rootless 模式允许以非 root 用户身份运行 Docker 守护进程(dockerd)和容器,以缓解 Docker 守护进程和容器运行时中潜在的漏洞。
Rootless 模式是在 Docker v19.03 版本作为实验性功能引入的,在 Docker v20.10 版本 GA。
Rootless 模式如何运作
Rootless 模式利用 user namespaces 将容器中的 root 用户和 Docker 守护进程(dockerd)用户映射到宿主机的非特权用户范围内。Docker 此前已经提供了 --userns-remap
标志支持了相关能力,提升了容器的安全隔离性。Rootless 模式在此之上,让 Docker 守护进程也运行在重映射的用户名空间中。
用户命名空间自 Linux 内核 v3.8 以来就已存在,因此该功能在 Docker 中已经存在很长时间了 。
Rootless 模式在用户名称空间内执行Docker守护程序和容器。这与userns-remap
mode非常相似,除了模式之外,userns-remap
守护进程本身以root特权运行,而在Rootless 模式下,守护程序和容器都在没有root特权的情况下运行。
Rootless 模式不使用具有SETUID
位或文件功能的二进制文件,除了newuidmap
和newgidmap
,它们是允许在用户名称空间中使用多个UID / GID所必需的。
Rootless 模式已知限制
-
• 仅支持以下存储驱动程序:
-
•
overlay2
(仅在以5.11或更高版本的内核,Ubuntu风格的内核或Debian风格的内核运行时) -
•
fuse-overlayfs
(仅在与内核4.18或更高版本一起运行且fuse-overlayfs
已安装的情况下) -
•
btrfs
(仅在使用内核4.18或更高版本运行,或~/.local/share/docker
通过user_subvol_rm_allowed
mount选项安装时) -
•
vfs
-
• 仅当与cgroup v2和systemd一起运行时,才支持Cgroup。请参阅限制资源。
-
• 不支持以下功能:
-
• AppArmor
-
• 检查站
-
• 叠加网络
-
• 暴露SCTP端口
-
• 要使用该
ping
命令,请参阅路由ping数据包。 -
• 要公开特权TCP / UDP端口(<1024),请参阅公开特权端口。
-
•
IPAddress
显示在中,docker inspect
并在RootlessKit的网络名称空间中命名。这意味着如果不nsenter
进入网络名称空间,则无法从主机访问IP地址。 -
• 主机网络(
docker run --net=host
)。
Rootless 模式实践
实践环境
本文使用 Centos 7.5 操作系统的虚拟机进行实验。
$ cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
创建普通用户
$ useradd rootless
$ echo 123456 | passwd rootless --stdin
安装依赖
Rootless 模式可以在没有 root 权限的情况下运行 Docker 守护进程和容器, 但是需要安装 newuidmap
和newgidmap
工具,以便在用户命名空间下创建从属(subordinate)用户和组的映射(remapping)。通过以下命令安装 newuidmap
和 newgidmap
工具。
cat <<EOF | sudo sh -x
curl -o /etc/yum.repos.d/vbatts-shadow-utils-newxidmap-epel-7.repo https://copr.fedorainfracloud.org/coprs/vbatts/shadow-utils-newxidmap/repo/epel-7/vbatts-shadow-utils-newxidmap-epel-7.repo
yum install -y shadow-utils46-newxidmap
cat <<EOT >/etc/sysctl.conf
user.max_user_namespaces = 28633
EOT
sysctl --system
EOF
注意事项
CentOS 7
-
• 添加
user.max_user_namespaces=28633
到/etc/sysctl.conf
(或/etc/sysctl.d
)并运行sudo sysctl --system
。 -
•
systemctl --user
默认情况下不起作用。dockerd-rootless.sh
不使用systemd直接运行。
CentOS 8 / Fedora
-
•
fuse-overlayfs
建议安装。运行sudo dnf install -y fuse-overlayfs
。 -
• 你可能需要
sudo dnf install -y iptables
。 -
• 启用SELinux后,你可能会遇到
can't open lock file /run/xtables.lock: Permission denied
错误。解决此问题的方法是sudo dnf install -y policycoreutils-python-utils && sudo semanage permissive -a iptables_t
。此问题已在moby / moby#41230中进行了跟踪。 -
• 已知可在CentOS 8和Fedora 33上工作。
Ubuntu
-
• 无需准备。
-
•
overlay2
默认情况下启用存储驱动程序(特定于Ubuntu的内核补丁)。 -
• 已知可在Ubuntu 16.04、18.04和20.04上运行
UID/GID 映射配置
从属用户和组的映射由两个配置文件来控制,分别是 /etc/subuid
和 /etc/subgid
。使用以下命令为 rootless 用户设置 65536 个从属用户和组的映射。
echo "rootless:100000:65536" | tee /etc/subuid
echo "rootless:100000:65536" | tee /etc/subgid
对于 subuid,这一行记录的含义为:用户 rootless,在当前的 user namespace 中具有 65536 个从属用户,用户 ID 为 100000-165535,在一个子 user namespace 中,这些从属用户被映射成 ID 为 0-65535 的用户。subgid 的含义和 subuid 相同。
比如说用户 rootless 在宿主机上只是一个具有普通权限的用户。我们可以把他的一个从属 ID (比如 100000 )分配给容器所属的 user namespace,并把 ID 100000 映射到该 user namespace 中的 uid 0。此时即便容器中的进程具有 root 权限,但也仅仅是在容器所在的 user namespace 中,一旦到了宿主机中,顶多也就有 rootless 用户的权限而已。
安装 Rootless Docker
切换到 rootless 用户。
su - rootless
执行以下命令安装 Rootless Docker。
curl -fsSL https://get.docker.com/rootless | sh
安装成功后显示如下内容。
$ curl -fsSL https://get.docker.com/rootless | sh
# Installing stable version 24.0.5
# Executing docker rootless install script, commit: b9139c0
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 66.5M 100 66.5M 0 0 1235k 0 0:00:55 0:00:55 --:--:-- 1373k
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 19.4M 100 19.4M 0 0 1096k 0 0:00:18 0:00:18 --:--:-- 1233k
+ PATH=/home/rootless/bin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/rootless/.local/bin:/home/rootless/bin
+ /home/rootless/bin/dockerd-rootless-setuptool.sh install
[INFO] systemd not detected, dockerd-rootless.sh needs to be started manually:
PATH=/home/rootless/bin:/sbin:/usr/sbin:$PATH dockerd-rootless.sh
[INFO] Creating CLI context "rootless"
Successfully created context "rootless"
[INFO] Using CLI context "rootless"
Current context is now "rootless"
[INFO] Make sure the following environment variable(s) are set (or add them to ~/.bashrc):
# WARNING: systemd not found. You have to remove XDG_RUNTIME_DIR manually on every logout.
export XDG_RUNTIME_DIR=/home/rootless/.docker/run
export PATH=/home/rootless/bin:$PATH
[INFO] Some applications may require the following environment variable too:
export DOCKER_HOST=unix:///home/rootless/.docker/run/docker.sock
将以下内容添加到 ~/.bashrc 文件中,添加完以后使用 source ~/.bashrc
命令使环境变量生效。
export XDG_RUNTIME_DIR=/home/rootless/.docker/run
export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///home/rootless/.docker/run/docker.sock
启动 Docker 守护进程
方式1:带systemd(强烈推荐)
systemd文件默认位置为 ~/.config/systemd/user/docker.service
。
使用systemctl --user
管理守护程序的生命周期:
$ systemctl --user start docker
要在系统启动时启动守护程序,请启用systemd服务并持续进行以下操作:
$ systemctl --user enable docker
$ sudo loginctl enable-linger $(whoami)
即使使用User=
指令,也不支持将Rootless Docker作为全系统范围的服务(/etc/systemd/system/docker.service
)启动。
关于目录路径的说明:
-
• 套接字默认路径为
$XDG_RUNTIME_DIR/docker.sock
。$XDG_RUNTIME_DIR
通常设置为/run/user/$UID
。 -
• 数据目录默认设置为
~/.local/share/docker
。数据目录不应位于NFS上。 -
• 守护程序配置目录默认设置为
~/.config/docker
。此目录~/.docker
与客户端使用的目录不同。
sudo loginctl enable-linger $(whoami)
解释
这个命令是用于启用用户的 linger(保持)设置,让用户的会话在用户注销后继续运行。让我详细解释一下这个命令的各个部分:
1.
sudo
:sudo
是一个用于以超级用户权限执行命令的工具。在这里,它用于确保我们有足够的权限来执行后续的命令。2.
loginctl
:loginctl
是一个用于管理 Linux 登录会话和用户登录状态的工具。它可以用于查看、控制和管理用户登录会话。3.
enable-linger
: 这是loginctl
的一个子命令,用于启用用户的 linger 设置。Linger 是一个控制登录会话是否在用户注销后继续运行的机制。启用 linger 后,用户注销后,其登录会话将继续运行,直到手动停止。4.
$(whoami)
: 这部分是一个命令替换,它会被当前登录用户的用户名所替代。whoami
命令用于获取当前登录用户的用户名。因此,整个命令的目的是以超级用户权限启用当前登录用户的 linger 设置,使其登录会话在用户注销后继续运行。这在某些情况下可能会很有用,例如,如果你希望在用户注销后继续运行某些后台任务或服务。请谨慎使用这个命令,因为它可能会导致系统资源被占用,特别是在多用户环境中。
方式2:没有systemd
dockerd-rootless.sh
必须设置以下环境变量:
-
•
$HOME
:用户目录 -
•
$XDG_RUNTIME_DIR
:临时目录,只有预期的用户可以访问,例如~/.docker/run
。该目录应在每次主机关闭时删除。该目录可以位于tmpfs上,但是不应位于之/tmp
下。在此/tmp
目录下,可能容易受到TOCTOU攻击。
客户端
需要明确指定套接字路径或CLI上下文。
要指定套接字路径,请使用$DOCKER_HOST
:
$ export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
$ docker run -d -p 8080:80 nginx
要指定CLI上下文,请使用docker context
:
$ docker context use rootless
rootless
Current context is now "rootless"
$ docker run -d -p 8080:80 nginx
运行容器
使用以下命令启动一个 nginx 容器,并将 80 端口映射到宿主机的 8080 端口。
docker run -d -p 8080:80 nginx
查看容器。
[rootless@demo ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f3b204c97a84 nginx "/docker-entrypoint.…" 9 minutes ago Up 9 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp bold_stonebraker
访问容器。
$ curl http://localhost:8080
# 返回结果 Nginx 欢迎界面
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Rootless 模式调试技巧
进入dockerd
名称空间
dockerd-rootless.sh
脚本,会在其自己的user,mount和network 名称空间中执行dockerd
。
为了进行调试,你可以通过以下明立进入名称空间Rootless 模式
nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid)
Rootless 模式卸载
要删除Docker守护程序的systemd服务,请运行dockerd-rootless-setuptool.sh uninstall
:
dockerd-rootless-setuptool.sh
默认位置:/home/rootless/bin/dockerd-rootless-setuptool.sh
$ dockerd-rootless-setuptool.sh uninstall
+ systemctl --user stop docker.service
+ systemctl --user disable docker.service
Removed /home/testuser/.config/systemd/user/default.target.wants/docker.service.
[INFO] Uninstalled docker.service
[INFO] This uninstallation tool does NOT remove Docker binaries and data.
[INFO] To remove data, run: `/usr/bin/rootlesskit rm -rf /home/testuser/.local/share/docker`
要删除数据目录,请运行rootlesskit rm -rf ~/.local/share/docker
。
要删除二进制文件,docker-ce-rootless-extras
请在软件包管理器中安装了Docker的情况下删除软件包。如果你使用https://get.docker.com/rootless安装了Docker,请删除以下二进制文件`~/bin`:
$ cd ~/bin
$ rm -f containerd containerd-shim containerd-shim-runc-v2 ctr docker docker-init docker-proxy dockerd dockerd-rootless-setuptool.sh dockerd-rootless.sh rootlesskit rootlesskit-docker-proxy runc vpnkit
Rootless Docker 与 Podman
RedHat Inc 的 Podman 是另一种流行的容器引擎,用于运行和管理容器。它被誉为以Rootless 运行是其相对于 docker 引擎的功能之一。通过Rootless Docker ,弥合了差距,现在它们具有几乎相同的功能和几乎相同的性能。他们之间还使用大量相同的代码。
Rootless Docker 不支持指定 docker run --net=host
。但如果你确实需要使用 docker run --net=host
,Podman 可能是你更好的选择。
总结
Docker Rootless模式是官方提供的一种安全解决方案,可以让Docker守护进程以普通用户身份运行,从而避免容器应用利用Docker漏洞获得宿主机root权限的风险。
另外,要注意的是因为Docker作为容器本身需要利用很多系统高级特性,因此Docker守护进程以非Root身份运行实际上也会导致一些功能受限。这点可以参与官方文档详细了解。