主要特性和优势
所有容器的 Linux 用户命名空间
启用增强型容器隔离后,所有用户容器都会利用 Linux 用户命名空间 以获得额外的隔离。这意味着容器中的 root 用户会映射到 Docker Desktop Linux VM 中的一个非特权用户。
例如
$ docker run -it --rm --name=first alpine
/ # cat /proc/self/uid_map
0 100000 65536
输出 0 100000 65536 是 Linux 用户命名空间的标识。它表示容器中的 root 用户 (0) 被映射到 Docker Desktop Linux VM 中的非特权用户 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 VM 中绝不以用户 ID 0(真正 root)身份运行。实际上,它们在 Linux VM 中不以任何有效用户 ID 身份运行。因此,它们的 Linux 能力仅限于容器内的资源,与常规容器相比,大大增强了容器到主机以及跨容器的隔离。
特权容器也受到保护
特权容器 docker run --privileged ... 不安全,因为它赋予容器对 Linux 内核的完全访问权限。也就是说,容器以真正 root 身份运行,启用了所有能力,seccomp 和 AppArmor 限制被禁用,所有硬件设备都暴露在外,等等。
希望确保开发者机器上 Docker Desktop 安全的组织面临特权容器带来的挑战。这些容器,无论运行的是良性工作负载还是恶意工作负载,都可能获得 Docker Desktop VM 内 Linux 内核的控制权,可能会更改与安全相关的设置,例如仓库访问管理和网络代理。
启用增强型容器隔离后,特权容器将无法再做到这一点。Linux 用户命名空间与 Sysbox 使用的其他安全技术的结合,确保特权容器内的进程只能访问分配给容器的资源。
注意
增强型容器隔离并不会阻止用户启动特权容器,而是通过确保它们只能修改与容器关联的资源来安全地运行它们。修改全局内核设置的特权工作负载,例如加载内核模块或更改 Berkeley Packet Filters (BPF) 设置,将无法正常工作,因为在尝试此类操作时会收到“权限被拒绝”错误。
例如,增强型容器隔离确保特权容器无法访问通过 BPF 在 Linux VM 中配置的 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 VM 共享命名空间
启用增强型容器隔离后,容器不能与主机共享 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 用户可以继续将主机目录绑定挂载到容器中,就像通过 Settings > Resources > File sharing 配置的那样,但不再允许将任意 Linux VM 目录绑定挂载到容器中。
这可以防止容器修改 Docker Desktop Linux VM 中的敏感文件,这些文件可能包含仓库访问管理、代理、Docker Engine 配置等信息。
例如,将 Docker Engine 配置文件(Linux VM 中的 /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 socket(
/var/run/docker.sock)绑定挂载到容器中,因为这样做会赋予容器对 Docker Engine 的控制权,从而破坏容器隔离。然而,由于某些合法的使用场景需要此功能,可以针对受信任的容器镜像放宽此限制。请参阅Docker socket 挂载权限。
审查敏感系统调用
增强型容器隔离的另一个特性是,它会拦截并审查容器内部一些高度敏感的系统调用,例如 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 VM 中“真实”用户 ID 的非特权范围(例如 100000->165535)。
此外,每个容器在 Linux VM 中都获得专属的真实用户 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 VM 内部的范围(0->65535)。这样,即使每个容器使用专属的用户 ID 范围,数据卷现在也可以在容器之间挂载或共享。用户无需担心容器的真实用户 ID。
尽管文件系统用户 ID 重映射可能导致容器以真实用户 ID 0 访问挂载到容器中的 Linux VM 文件,但受限挂载功能确保敏感的 Linux VM 文件无法挂载到容器中。
Procfs & sysfs 模拟
增强型容器隔离的另一个特性是,在每个容器内部,/proc 和 /sys 文件系统会进行部分模拟。这有几个目的,例如隐藏容器内部的敏感主机信息,以及对 Linux 内核本身尚未命名空间化的主机内核资源进行命名空间化。
举个简单的例子,启用增强型容器隔离后,/proc/uptime 文件显示的是容器本身的运行时长,而不是 Docker Desktop Linux VM 的运行时长。
$ docker run -it --rm alpine
/ # cat /proc/uptime
5.86 5.86
相反,如果没有增强型容器隔离,您会看到 Docker Desktop Linux VM 的运行时长。尽管这是一个简单的例子,但它显示了增强型容器隔离如何旨在防止 Linux VM 的配置和信息泄露到容器中,从而更难攻破 VM。
此外,/proc/sys 下的一些 Linux 内核尚未命名空间化的其他资源也在容器内部进行模拟。每个容器看到的是这些资源的独立视图,Sysbox 在编程相应的 Linux 内核设置时会协调跨容器的值。
这样做的好处是,原本需要真正特权容器才能访问此类非命名空间化内核资源的容器工作负载,现在可以在启用增强型容器隔离的情况下运行,从而提高了安全性。