Kubernetes 深入浅出系列 | 容器剖析之容器安全

目录

    • 1、容器真的需要privileged权限吗?
      • 一、什么是 --privileged 权限?
      • 二、privileged 的风险到底有多大?
      • 三、常见需求场景及更安全的替代方式
      • 四、如何判断容器是否真正需要特权?
    • 2、不以 Root 用户运行容器,真的更安全吗?
      • 一、为什么Docker 默认是 root 用户?
      • 二、不以 root 用户运行容器,有何好处?
      • 三、真正的容器安全建议
    • 3、容器安全实践:User Namespace 与 Rootless 技术
      • 一、为什么建议在容器中避免使用 root 用户?
      • 二、 User Namespace 的价值与优势
      • 三、Rootless 容器的进一步增强
      • 四、总结建议
    • 4、Rootless 模式的实践指南(systemd、Kubernetes 场景)
      • 一、 基于 systemd 的 Rootless 容器启动
      • 二、配置 subuid/subgid:UID/GID 映射的前提
      • 三、在 Kubernetes 环境中应用 Rootless 容器

容器提升了交付效率,但不当使用 --privileged 或以 root 用户运行,可能带来严重安全隐患。本文系统解析容器特权机制风险,并介绍 User Namespace 与 Rootless 等实用加固方案,助你构建更安全的容器环境。

1、容器真的需要privileged权限吗?

容器技术的发展极大推动了应用部署与交付的效率。然而,在安全性方面,很多人在构建容器镜像或部署容器时,常常会习惯性地加上 --privileged 参数,却对其实际含义和潜在风险了解不足。

本文将带你深入理解 privileged 权限的本质、风险以及更安全的替代方案。

一、什么是 --privileged 权限?

在默认情况下,容器受到多层隔离,包括 Linux Namespace、cgroups 和 seccomp 等机制,无法访问宿主机的大多数敏感资源。然而,当你为容器添加 --privileged 参数后,这些限制就会被全部解除。

具体来说,--privileged 会:

赋予容器所有 Linux Capabilities;

授权访问宿主机所有设备节点(/dev);

允许容器修改内核参数(如 sysctl)和执行挂载;

如果挂载了 docker.sock,还可以控制宿主机上的 Docker 引擎;

允许加载内核模块、访问 kmsg 等系统级资源。

简而言之,这个参数让容器具备了几乎和宿主机一致的能力。

二、privileged 的风险到底有多大?

从安全角度看,使用 --privileged 就像是在"裸奔"。

它绕过了 Linux Namespace 和 cgroup 的多个隔离机制。一旦容器被攻破,攻击者可以轻松"跳脱"容器,控制整个宿主机。例如:

利用 /dev/kmsg 或内核模块注入绕过安全限制;

利用 nsenter 进入主机其他命名空间;

通过 docker.sock 创建任意特权容器;

利用 cgroup 子系统逃逸至宿主机。

💡结论:非必要,绝不使用 --privileged!

三、常见需求场景及更安全的替代方式

虽然大多数应用并不需要 --privileged,但确实存在一些需要较高权限的场景。下面我们来看几类典型需求,并给出更合理的解决方案。

  1. 需要挂载宿主机目录或设备
    **替代方案:**使用 --cap-add 精细赋权 + 显式挂载 --device

    docker run --cap-add=SYS_ADMIN --device=/dev/fuse mycontainer

常用的 Capabilities:

网络配置修改 NET_ADMIN

进程调试、gdb SYS_PTRACE

内核参数修改 SYS_ADMIN(慎用)

创建设备节点 MKNOD

  1. 需要访问宿主机 Docker 引擎

该场景风险极高,强烈建议避免将宿主机 Docker socket 暴露给容器。推荐使用 containerd + CRI 接口,通过容器运行时安全隔离访问容器资源,并结合 API 网关或 Sidecar 模式完成管理需求。

  1. 需要进行网络、进程、命名空间调试(如 nsenter)

    **替代方案:**为容器添加 cap_add: SYS_PTRACE 权限,或使用特权较低的调试容器 + hostPID。

    复制代码
    securityContext:
      capabilities:
        add: ["SYS_PTRACE"]
      hostPID: true
  2. 在 Kubernetes 中使用特权容器

    更安全方式:

    复制代码
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
        add: ["NET_ADMIN"]  # 按需添加

控制入口:

使用 PodSecurity Admission 替代已弃用的 PSP;

或通过 OPA Gatekeeper / Kyverno 强制执行权限规范;

利用 runtimeClass 使用如 gVisor、Kata Containers 实现沙箱隔离;

引入 KubeArmor、Tetragon 等安全模块做系统调用约束;

四、如何判断容器是否真正需要特权?

给出几个建议步骤供参考:

分析容器内应用行为:是否涉及系统级操作?是否访问设备?

采用最小权限原则:用 --cap-add 和 --device 替代完整特权。

结合 seccomp、AppArmor 或 SELinux 进行约束。

审计 Dockerfile 与运行命令,避免无脑加上 --privileged。

2、不以 Root 用户运行容器,真的更安全吗?

在容器中运行程序时,很多人听说过"不要以 root 用户身份运行",这句话常被奉为最佳实践。那么,这背后的安全逻辑是什么?

一、为什么Docker 默认是 root 用户?

Docker 默认会以 root 用户运行容器中的进程。这种默认行为有历史与兼容性原因:

容器中的 root 与宿主机 root UID 相同(UID=0),这就意味着容器中运行的 root 用户理论上有机会获取宿主机的 root 权限,尤其当容器配置不当时(如使用 --privileged、挂载宿主机路径等)。

复制代码
#容器中用户的uid/gid和宿主机上的完全一样;
# docker run -d --name root_example -v /etc:/mnt centos sleep 3600

# docker exec -it root_example bash -c "ps -ef | grep sleep"
root          1     0  0 01:14 ?        00:00:00 /usr/bin/coreutils --

# ps -ef | grep sleep
root       5473  5443  0 18:14 ?        00:00:00 /usr/bin/coreutils --

虽然容器里root用户的capabilities被限制了一些,但是在容器器中,对于被挂载上来的/eto

目录下的文件,比如说shadow文件,以这个root用户的权限还是可以做修改的。

复制代码
# docker exec -it root_example bash
[root@9c7b76232c19 /]# ls /mnt/shadow -l
---------- 1 root root 586 Nov 26 13:47 /mnt/shadow

[root@9c7b76232c19 /]# echo "hello" >> /mnt/shadow

接着在宿主机上查看/etc/shadow文件已经被修改了,这个例子说明容器中的root用户也有权限修改宿主机上的关键文件。

二、不以 root 用户运行容器,有何好处?

表面上,不以 root 用户运行容器,确实可以减轻以下几类攻击带来的风险:

  • 攻击者即便控制了容器内的应用,由于权限受限,难以访问容器内的系统资源;
  • 降低了通过容器逃逸攻击宿主机的可能性;
  • 配合 Linux 内核的安全模块(如 AppArmor、SELinux、seccomp、capabilities 等)时,效果更佳。

因此,在常规场景下,确实推荐创建一个非特权用户来运行容器进程。

⚠️ 这就"安全"了吗?

事实并非如此简单。

如果你通过如下方式切换用户运行容器:

复制代码
RUN useradd -m appuser
USER appuser

但却仍使用以下 Docker 启动命令:

复制代码
docker run --privileged -v /:/host ...

那么,攻击者可以通过容器中的程序或漏洞获得宿主机的 root 权限,即便你不以 root 运行容器进程,因为容器本身拥有过大的能力。

换句话说:

容器的宿主权限(宿主资源访问权限)比容器内进程使用哪个 UID 更关键!

🚨 常见误区举例

误区 实际情况
以非 root 用户运行容器就很安全 如果容器配置不当(如挂载宿主路径、使用 --privileged),仍然存在严重风险
rootless 容器一定安全 如果容器逃逸漏洞存在,攻击者依然有可能通过内核提权等手段取得更高权限
改掉 USER 就够了 USER 只是容器中进程的运行身份,和容器整体的特权配置、挂载机制等都有关

三、真正的容器安全建议

  • 避免使用 --privileged 模式运行容器。
  • 限制宿主机目录挂载 (尤其是 /, /proc, /sys 等)。
  • 使用用户命名空间(User Namespaces) 实现容器内 root 与宿主机 root 的隔离。
  • 结合 AppArmor、seccomp、SELinux、capabilities 等 限制容器权限。
  • 考虑采用 Rootless 模式运行容器(例如 Podman、Rootless Docker)。

🧩 结语

不以 root 用户运行容器,虽然是一个重要的安全措施,但它不是万能的。真正的容器安全依赖于多层防御(Defense in Depth),包括用户权限、容器配置、内核机制、资源隔离等多个维度。

最安全的容器,不只是"不用 root",而是"不信任容器内的任何进程"。

3、容器安全实践:User Namespace 与 Rootless 技术

虽然容器中的 root 用户在默认情况下已经被限制了一些 Linux capabilities,但在未启用 User Namespace 的情况下,容器中的 root 用户 UID=0 与宿主机的 root 用户是完全一致的。这意味着,一旦容器内应用存在漏洞,攻击者可能突破容器边界,直接威胁宿主机。

一、为什么建议在容器中避免使用 root 用户?

为了减少此类安全风险,业界普遍建议容器中的进程不要以 root 用户身份运行。然而,在不启用 User Namespace 的情况下,使用非 root 用户虽然能提供隔离,但也带来了一定的 UID/GID 映射管理负担,尤其在多租户平台或 CI/CD 场景下更为复杂。

二、 User Namespace 的价值与优势

现代容器引擎(如 Docker、containerd、Podman)已广泛支持 User Namespace技术,其核心作用是:

  1. 将容器内的 root 用户映射为宿主机上的非特权用户(例如 UID 100000),即使容器被攻破,攻击者也无法直接控制宿主系统资源;
  2. 提升多租户环境下的 UID/GID 分配灵活性,使云原生平台更易于管理容器资源和隔离权限。

三、Rootless 容器的进一步增强

除了在容器内运行非 root 进程,现在主流容器工具(如 Docker、Podman、Buildah、nerdctl 等)都已支持 Rootless 模式运行容器引擎本身。也就是说:

开发者和运维人员可以完全不使用 root 权限,就能创建、运行和管理容器。

这大大降低了容器守护进程本身被攻击的风险,是构建"最小信任容器运行环境"的关键路径之一。


四、总结建议

  • 优先启用 User Namespace,实现容器 root 用户与宿主机权限的真正隔离;
  • 尽量以非 root 用户运行容器内进程
  • 在支持的环境下优先采用 Rootless 模式运行 Docker/Podman 等引擎本身,将宿主机的攻击面进一步压缩到最小。

4、Rootless 模式的实践指南(systemd、Kubernetes 场景)

一、 基于 systemd 的 Rootless 容器启动

在现代 Linux 系统中,推荐使用 systemd --user 启动 Rootless 容器守护进程。以 Rootless Docker 为例:

bash 复制代码
# 初始化 rootless 环境(仅需一次)
$ dockerd-rootless-setuptool.sh install

# 启动 docker rootless 守护进程(通过 systemd user 服务)
$ systemctl --user start docker
$ systemctl --user enable docker

然后添加环境变量,让 CLI 工具连接 rootless docker:

bash 复制代码
export DOCKER_HOST=unix:///run/user/1000/docker.sock

二、配置 subuid/subgid:UID/GID 映射的前提

要启用 User Namespace 或 Rootless 模式,必须配置 UID/GID 映射。编辑如下两个文件:

bash 复制代码
/etc/subuid
/etc/subgid

为当前用户配置:

bash 复制代码
your_user:100000:65536

表示将容器内的 UID 从 0 开始映射到宿主机的 UID 100000,共 65536 个 UID。GID 同理。

你也可以通过 usermod 命令自动添加:

bash 复制代码
sudo usermod --add-subuids 100000-165536 your_user
sudo usermod --add-subgids 100000-165536 your_user

三、在 Kubernetes 环境中应用 Rootless 容器

✅ CRI-O 支持 User Namespace 和 Rootless

CRI-O 原生支持以非 root 用户运行容器;

需启用内核功能与相应配置,如:

  • enable_userns: true(/etc/crio/crio.conf)
  • 配置 /etc/subuid 和 /etc/subgid

✅ K3s 轻量级 K8s 发行版支持 Rootless

✅ kind(Kubernetes IN Docker)

虽然 kind 本身默认使用 Docker,但你可以通过以下方式运行在 Rootless Docker 上:

bash 复制代码
export DOCKER_HOST=unix:///run/user/1000/docker.sock
kind create cluster

此时整个 kind 集群也运行在非 root 权限容器之上,进一步降低系统攻击面。

写作不易,点点关注,持续更新中🌹
参考资料:

K3s 官方文档:https://docs.k3s.io/zh/advanced#启动-rootless-server

Docker Rootless 模式官方文档:https://docs.docker.com/engine/security/rootless/

kind Rootless 支持: https://kind.sigs.k8s.io/docs/user/rootless/

相关推荐
运维开发故事3 天前
基于 Arthas 的多集群在线诊断系统设计与实现
kubernetes
Flynt3 天前
npm v12 来了:allowScripts 默认关闭,我的项目差点跑不起来
安全·npm·node.js
Patrick_Wilson5 天前
从「改个端口」到 502:Next.js on k8s 的容器端口、Service 映射与 env 覆盖
docker·kubernetes·next.js
探索云原生5 天前
K8s 1.36 这个 GA 特性,把 initContainer 拉模型的 hack 干掉了
ai·云原生·kubernetes
云恒要逆袭5 天前
运行你的第一个Docker容器
后端·docker·容器
Java之美6 天前
一次k8s升级引发的DevicePlugin注册失败
云原生·kubernetes
程序员老赵7 天前
10 分钟部署 OpenCode:Docker 一键安装,浏览器打开就能用 AI 写代码(附完整命令与排错)
docker·容器·ai编程
冬奇Lab8 天前
Skill 系列(02):Skill 安全风险——三类攻击面的实战测试
人工智能·安全·开源
武子康10 天前
调查研究-183 Apple container:Mac 上用轻量 VM 跑 Linux 容器,Swift 会改写本地容器体验吗?
docker·容器·apple