4.4 KiB
18.1 内核命名空间
命名空间 (Namespace) 是 Linux 容器隔离的基础,它确保了容器内的进程无法直接干扰主机或其他容器。虽然在本书第 12 章中我们已经从底层实现的角度介绍了 Namespace,但在本节中,我们将重点探讨其安全意义及相关配置。
18.1.1 隔离的安全本质
Docker 守护进程在启动容器时,会在后台为容器创建一套独立的命名空间。命名空间提供了最基础也是最直接的隔离:
- PID Namespace:防止容器内的进程查看或终止宿主机或其他容器的进程。恶意攻击者即使在容器内获得了 root 权限,也无法通过
kill命令影响宿主机上的关键服务。 - NET Namespace:每个容器都有自己独立的网络栈。如果没有显式地进行端口映射或将容器连接到同一网络,容器之间无法网络互通,从而限制了横向移动的能力。
- MNT Namespace:为容器提供独立的文件系统视图。这可以防止容器不经意或恶意地修改宿主机的重要系统文件(如
/etc/passwd)。
18.1.2 命名空间不是绝对安全的护城河
尽管命名空间提供了很好的隔离性,但我们必须认识到:所有的容器依然共享同一个宿主机的 Linux 内核。
这意味着,一旦宿主机的内核存在提权漏洞(如著名的 Dirty COW 漏洞),攻击者有可能通过突破 Namespace 的限制,直接在内核层面执行恶意代码,从而实现“容器逃逸”。
Warning
为了缓解内核漏洞带来的威胁,生产环境务必保持宿主机 Linux 内核的及时修补与更新,或者借助诸如 gVisor、Kata Containers 等提供了独立内核的安全容器技术。
通过命名空间,Docker 也能限制进程从外部环境获取信息。 例如,由于进程环境被隔离,进程在内部其实是无法感知到外部宿主机的存在的。它既不能获取其他容器的进程列表,也无法通过网络与其他系统进行交互(除非经过配置)。
18.1.1 用户命名空间 与提权防护
在所有的 Namespace 中,User Namespace 对安全的影响尤为关键。
在默认情况下,容器内的 root 用户(UID=0)就是宿主机上的 root 用户。如果攻击者设法突破了容器的其他隔离机制获取了宿主机的访问权限,他将拥有宿主机的最高系统权限。
通过启用 User Namespace Remapping (用户命名空间映射),我们可以将容器内的 root 用户映射到宿主机上的一个无特权普通用户。
如何配置 User Namespace
要在 Docker 服务端启用这一特性,需要修改 Docker 的配置文件 /etc/docker/daemon.json。
- 设置映射策略
编辑配置文件,添加 userns-remap 配置项:
{
"userns-remap": "default"
}
使用 default 值时,Docker 会自动在宿主机上创建一个名为 dockremap 的用户和用户组。
- 验证子 UID 和子 GID 分配
Docker 会通过 /etc/subuid 和 /etc/subgid 文件为 dockremap 分配一个高位的 UID 范围:
$ cat /etc/subuid
dockremap:165536:65536
这意味着:容器内的 UID 0(root 用户)在宿主机上实际被映射成了 UID 165536。如果是容器内的 UID 1,对应宿主机的 165537,以此类推。
- 重启 Docker 守护进程
$ sudo systemctl restart docker
验证映射效果
我们可以运行一个简单的容器并执行 sleep 命令,同时在宿主机上观察进程的所有者:
## 在容器内以 root 身份运行
$ docker run -d --name userns_test alpine sleep 3600
## 在宿主机上查看该 sleep 进程
$ ps aux | grep sleep
165536 12345 0.0 0.0 1568 4 ? Ss 14:20 0:00 sleep 3600
你会发现,尽管在容器内该进程是由 root 启动的,但在宿主机上,它的属主是 165536(一个完全没有特权的用户)。
Tip
启用 User Namespace 会对容器共享宿主机数据卷(Bind Mount)产生权限影响。你需要确保映射后的高位 UID 对宿主机上的挂载目录具有合适的读写权限。
18.1.4 总结
内核命名空间从 Linux 2.6.15 版本 (2006 年) 被引入,十余年间,这些机制的可靠性在诸多大型生产系统中被实践验证。通过合理利用命名空间(尤其是 User Namespace),可以极大地收窄攻击面,显著提升容器部署的安全性。