主要特性与优势
所有容器上的 Linux 用户命名空间
通过增强的容器隔离功能,所有用户容器均可利用 Linux 用户命名空间 实现额外隔离。这意味着容器中的 root 用户将映射为 Docker Desktop Linux 虚拟机中的非特权用户。
例如:
$ docker run -it --rm --name=first alpine
/ # cat /proc/self/uid_map
0 100000 65536
输出 0 100000 65536 是 Linux 用户命名空间的签名。这意味着容器中的根用户(0)被映射到 Docker Desktop Linux 虚拟机中非特权用户 100000,且该映射覆盖连续范围的 64K 个用户 ID。组 ID 也适用相同的规则。
每个容器都会获得一个由 Sysbox 管理的独占映射范围。例如,如果启动第二个容器,其映射范围将有所不同:
$ docker run -it --rm --name=second alpine
/ # cat /proc/self/uid_map
0 165536 65536
相比之下,如果没有增强型容器隔离功能,容器的 root 用户实际上就是主机上的 root(即“真实 root”),并且这一情况适用于所有容器:
$ docker run -it --rm alpine
/ # cat /proc/self/uid_map
0 0 4294967295
借助 Linux 用户命名空间,增强型容器隔离确保容器进程在 Linux 虚拟机中永远不会以用户 ID 0(真正的 root)运行。事实上,它们在 Linux 虚拟机中不会以任何有效的用户 ID 运行。因此,它们的 Linux 能力仅限于容器内的资源,与普通容器相比,显著提升了隔离性,包括容器到主机以及跨容器的隔离。
特权容器同样受到保护
特权容器 docker run --privileged ... 是不安全的,因为它们赋予容器对 Linux 内核的完全访问权限。也就是说,容器以真正的 root 身份运行,启用了所有能力,禁用了 seccomp 和 AppArmor 限制,并且例如暴露了所有硬件设备。
旨在保护开发人员机器上 Docker Desktop 的组织面临着特权容器带来的挑战。这些容器,无论运行的是良性还是恶意工作负载,都可能获得 Docker Desktop 虚拟机内 Linux 内核的控制权,从而潜在地更改与安全相关的设置,例如注册表访问管理和网络代理。
通过增强的容器隔离技术,特权容器不再能够执行此操作。Linux 用户命名空间与 Sysbox 采用的其他安全技术的结合,确保了特权容器内的进程只能访问分配给该容器的资源。
注意
增强的容器隔离性并不能阻止用户启动特权容器,而是通过确保它们只能修改与容器关联的资源来安全地运行这些容器。例如,加载内核模块或更改伯克利数据包过滤器 (BPF) 设置等会修改全局内核设定的特权工作负载将无法正常运行,因为在尝试执行此类操作时,它们会收到“权限被拒绝”的错误。
例如,增强的容器隔离功能可确保特权容器无法访问通过 BPF 配置的 Linux 虚拟机中的 Docker Desktop 网络设置:
$ docker run --privileged djs55/bpftool map show
Error: can't get next map: Operation not permitted
相比之下,如果没有增强的容器隔离,特权容器可以轻易做到这一点:
$ docker run --privileged djs55/bpftool map show
17: ringbuf name blocked_packets flags 0x0
key 0B value 0B max_entries 16777216 memlock 0B
18: hash name allowed_map flags 0x0
key 4B value 4B max_entries 10000 memlock 81920B
20: lpm_trie name allowed_trie flags 0x1
key 8B value 8B max_entries 1024 memlock 16384B
请注意,某些高级容器工作负载需要特权容器, 例如 Docker-in-Docker、Kubernetes-in-Docker 等。通过增强型容器 隔离功能,您仍然可以运行此类工作负载,但安全性远高于以往。
容器无法与 Linux 虚拟机共享命名空间
当启用增强型容器隔离时,容器无法与主机共享 Linux 命名空间(例如 PID、网络、uts 等),因为这本质上会破坏隔离性。
例如,共享 PID 命名空间会失败:
$ docker run -it --rm --pid=host alpine
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: error in the container spec: invalid or unsupported container spec: sysbox containers can't share namespaces [pid] with the host (because they use the linux user-namespace for isolation): unknown.
同样,共享网络命名空间也会失败:
$ docker run -it --rm --network=host alpine
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: error in the container spec: invalid or unsupported container spec: sysbox containers can't share a network namespace with the host (because they use the linux user-namespace for isolation): unknown.
此外,用于在容器上禁用用户命名空间的 --userns=host 标志将被忽略:
$ docker run -it --rm --userns=host alpine
/ # cat /proc/self/uid_map
0 100000 65536
最后,不允许使用 Docker build --network=host 和 Docker buildx 授权(network.host、security.insecure)。需要这些功能的构建将无法正常工作。
绑定挂载限制
当启用增强型容器隔离功能时,Docker Desktop 用户仍可继续通过 设置 > 资源 > 文件共享 配置将主机目录绑定挂载到容器中,但不再允许将任意的 Linux 虚拟机目录绑定挂载到容器中。
这可以防止容器修改 Docker Desktop Linux 虚拟机内的敏感文件,这些文件可能包含用于注册表访问管理、代理、Docker Engine 配置等的设置。
例如,将 Docker Engine 的配置文件(Linux 虚拟机内的 /etc/docker/daemon.json)绑定挂载到容器中会受到限制,因此会失败:
$ docker run -it --rm -v /etc/docker/daemon.json:/mnt/daemon.json alpine
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: error in the container spec: can't mount /etc/docker/daemon.json because it's configured as a restricted host mount: unknown
相比之下,如果没有增强型容器隔离,此挂载将生效,并赋予容器对 Docker Engine 配置的完全读写权限。
当然,主机文件的绑定挂载仍然照常工作。例如,
假设用户将 Docker Desktop 配置为共享她的 $HOME 目录,
她可以将其绑定挂载到容器中:
$ docker run -it --rm -v $HOME:/mnt alpine
/ #
注意
默认情况下,增强型容器隔离功能不允许将 Docker Engine 套接字 (
/var/run/docker.sock) 绑定挂载到容器中,因为这样做实质上会授予容器对 Docker Engine 的控制权,从而破坏容器隔离。然而,由于某些合法用例需要此功能,因此可以为受信任的容器镜像放宽此限制。请参阅 Docker 套接字挂载权限。
审查敏感的系统调用
增强型容器隔离的另一项功能是,它会拦截并审查容器内的一些高度敏感的系统调用,例如 mount 和
umount。这确保了拥有执行这些系统调用能力的进程无法利用它们来突破容器隔离。
例如,一个具有 CAP_SYS_ADMIN(执行 mount 系统调用所必需)的容器无法使用该能力将只读绑定挂载更改为读写挂载:
$ docker run -it --rm --cap-add SYS_ADMIN -v $HOME:/mnt:ro alpine
/ # mount -o remount,rw /mnt /mnt
mount: permission denied (are you root?)
由于 $HOME 目录是以只读方式挂载到容器的 /mnt 目录中的,因此即使容器进程具备相应权限,也无法在容器内部将其更改为读写模式。这可确保容器进程无法利用 mount 或 umount 来突破容器的根文件系统。
但请注意,在前面的示例中,容器仍然可以在其内部创建挂载点,并根据需要以只读或读写方式挂载它们。这些挂载是被允许的,因为它们发生在容器内部,因此不会突破其根文件系统:
/ # mkdir /root/tmpfs
/ # mount -t tmpfs tmpfs /root/tmpfs
/ # mount -o remount,ro /root/tmpfs /root/tmpfs
/ # findmnt | grep tmpfs
├─/root/tmpfs tmpfs tmpfs ro,relatime,uid=100000,gid=100000
/ # mount -o remount,rw /root/tmpfs /root/tmpfs
/ # findmnt | grep tmpfs
├─/root/tmpfs tmpfs tmpfs rw,relatime,uid=100000,gid=100000此功能与用户命名空间一起,确保即使容器进程拥有所有 Linux 能力,也无法利用这些能力突破容器限制。
最后,增强型容器隔离以这样一种方式进行系统调用审查:在绝大多数情况下,它不会影响容器的性能。它会拦截在大多数容器工作负载中很少使用的控制路径系统调用,但不会拦截数据路径系统调用。
文件系统用户 ID 映射
如前所述,ECI 在所有容器上启用了 Linux 用户命名空间。这可确保容器的用户 ID 范围(0->64K)映射到 Docker Desktop Linux 虚拟机中非特权的“真实”用户 ID 范围(例如 100000->165535)。
此外,每个容器在 Linux 虚拟机中都会获得一个独占的真实用户 ID 范围(例如,容器 0 可能映射到 100000->165535,容器 2 映射到 165536->231071,容器 3 映射到 231072->296607,依此类推)。组 ID 也适用同样的规则。另外,如果容器被停止并重新启动,无法保证它会获得与之前相同的映射关系。这是设计使然,并进一步提升了安全性。
然而,这在将 Docker 卷挂载到容器中时会带来问题。写入此类卷的文件具有真实的用户/组 ID,因此由于每个容器的真实用户 ID/组 ID 不同,这些文件在容器启动/停止/重启之间或在不同容器之间将无法访问。
为解决此问题,Sysbox 通过 Linux 内核的 ID 映射挂载功能(2021 年新增)或替代的 shiftsfs 模块,使用“文件系统用户 ID 重映射”技术。这些技术将来自容器的真实用户 ID(例如范围 100000->165535)的文件系统访问映射到 Docker Desktop 的 Linux 虚拟机内的范围(0->65535)。这样一来,即使每个容器使用独占的用户 ID 范围,卷现在也可以跨容器挂载或共享。用户无需担心容器的真实用户 ID。
尽管文件系统用户 ID 重映射可能导致容器以真实用户 ID 0 访问挂载到容器中的 Linux 虚拟机文件,但 受限挂载功能 确保敏感的 Linux 虚拟机文件无法被挂载到容器中。
Procfs 和 sysfs 模拟
增强型容器隔离的另一项功能是,在每个容器内部,/proc 和 /sys 文件系统被部分模拟。此举具有多种用途,例如在容器内隐藏敏感的主机信息,以及对尚未由 Linux 内核本身进行命名空间隔离的主机内核资源进行命名空间隔离。
作为一个简单的示例,当启用“增强型容器隔离”时,
/proc/uptime 文件显示的是容器自身的运行时间,而非
Docker Desktop Linux 虚拟机的运行时间:
$ docker run -it --rm alpine
/ # cat /proc/uptime
5.86 5.86
相比之下,如果没有启用增强型容器隔离,您将看到 Docker Desktop Linux 虚拟机的正常运行时间。虽然这是一个简单的示例,但它展示了增强型容器隔离如何旨在防止 Linux 虚拟机的配置和信息泄露到容器中,从而增加攻破该虚拟机的难度。
此外,/proc/sys 下的其他一些未被 Linux 内核命名空间化的资源也会在容器内被模拟。每个容器都会看到这些资源的独立视图,而 Sysbox 在配置相应的 Linux 内核设置时,会协调各个容器之间的值。
这使得原本需要真正特权容器才能访问此类非命名空间内核资源的容器工作负载,能够在启用增强型容器隔离的情况下运行,从而提升安全性。