Linux容器安全纵深防御实战:runc逃逸漏洞分析与Seccomp、AppArmor加固指南

从2025年runc容器逃逸漏洞(CVE-2025-31133等)出发,系统讲解Seccomp系统调用过滤、AppArmor/SELinux强制访问控制、Podman无根容器及Kubernetes Pod安全标准的实战配置,帮你构建可落地的容器安全纵深防御方案。

容器安全加固完整指南2026:runc逃逸与Seccomp

引言:容器安全——你的隔离真的可靠吗?

容器技术已经成了现代基础设施的基石,这一点没人会否认。从Docker到Kubernetes,从微服务到CI/CD流水线,容器几乎无处不在。但说实话,有一个事实经常被大家忽略:容器的隔离本质上是基于Linux内核的命名空间(namespace)和控制组(cgroup)实现的——它不是真正意义上的虚拟化,更不是硬件级隔离。

容器和宿主机共享同一个内核。一旦内核层面出了问题,容器的边界就可能被突破。

2025年11月,安全研究人员披露了三个影响runc容器运行时的高危漏洞(CVE-2025-31133、CVE-2025-52565、CVE-2025-52881),攻击者可以利用挂载竞态条件和procfs写入重定向实现容器逃逸,直接获取宿主机root权限。这不是第一次,也绝不会是最后一次——回顾过去几年,从CVE-2019-5736到CVE-2024-21626再到最新这一批,runc差不多每隔一段时间就会曝出容器逃逸漏洞。说真的,如果你还觉得"容器隔离就足够了",是时候改变这个想法了。

面对这样的现实,光靠容器本身的命名空间隔离远远不够。我们需要的是纵深防御——在容器运行时的每一层都部署独立的安全控制,这样即使某一层被攻破,攻击者也没法轻易到达下一层。

本文将从最新的容器逃逸漏洞分析入手,系统讲解Seccomp系统调用过滤、AppArmor/SELinux强制访问控制、无根容器技术,以及Kubernetes Pod安全标准的实战配置。不管你是运维单台Docker主机还是管理大规模K8s集群,都能在这里找到可以直接落地的加固方案。那么,让我们开始吧。

第一章:容器隔离的本质与最新逃逸漏洞剖析

1.1 容器隔离机制回顾

在聊安全加固之前,先搞清楚容器到底是怎么实现"隔离"的。Linux容器依赖下面这些内核机制:

  • 命名空间(Namespaces):提供进程ID、网络、挂载点、用户ID、主机名、IPC和cgroup的隔离视图。简单说,容器内的进程看不到宿主机或其他容器的资源
  • 控制组(cgroups):限制容器可以使用的CPU、内存、IO和网络带宽等资源
  • Linux Capabilities:把传统的超级用户权限拆分成细粒度的能力集合,容器默认只获得一个受限的子集
  • Seccomp:过滤容器进程可以使用的系统调用
  • LSM(Linux安全模块):通过AppArmor或SELinux提供强制访问控制

这里的关键问题在于:命名空间和cgroup只提供了资源视图的隔离,它们并不是专门为安全隔离设计的。Linux内核暴露了数百个系统调用,每一个都是潜在的攻击面。只要某个系统调用的实现存在漏洞,攻击者就能从容器内部利用它突破隔离边界。

1.2 runc 2025年容器逃逸漏洞深度分析

2025年11月5日,SUSE安全研究员披露了三个runc高危漏洞,影响Docker、Kubernetes以及所有使用runc作为底层运行时的容器平台。这三个漏洞都涉及竞态条件,让我逐个分析。

CVE-2025-31133:masked path挂载竞态条件

runc在创建容器时会对某些敏感路径(比如/proc/acpi/proc/scsi等)进行"掩码"处理——在这些路径上挂载一个空的只读tmpfs或/dev/null,防止容器进程读取宿主机的敏感信息。CVE-2025-31133利用的恰恰是这个掩码过程中的竞态窗口:

  1. 攻击者在容器镜像中预置一个恶意的init进程
  2. runc启动容器并开始设置masked paths
  3. 在runc完成掩码挂载之前(这个时间窗口虽然很短,但足够了),恶意进程通过符号链接将挂载目标重定向到/proc/self/exe——也就是runc二进制本身
  4. runc最终把可写挂载绑定到了错误的目标上,攻击者获得了对runc二进制的写权限
  5. 攻击者覆写runc二进制,下次runc执行时就获得了宿主机root权限

CVE-2025-52565:/dev/console绑定挂载利用

这个漏洞跟/dev/console设备的绑定挂载过程有关。攻击者用类似的竞态条件和符号链接操纵手法,在安全保护完全生效之前,让runc将意外的目标挂载到容器内部,创建出一个可读写的procfs绑定,从而实现逃逸。

CVE-2025-52881:sysctl写入任意文件重定向

坦白说,这可能是三个漏洞中最危险的一个。攻击者利用共享挂载技巧,将runc的sysctl写入操作重定向到宿主机上的任意文件。最致命的攻击路径包括:

  • 写入/proc/sysrq-trigger:直接触发系统崩溃(内核恐慌)
  • 写入/proc/sys/kernel/core_pattern:当程序崩溃时执行攻击者指定的程序,实现任意代码执行
  • 绕过LSM标签:让runc在没有正确安全标签的情况下写入文件
# 检测系统是否受影响:检查runc版本
runc --version
# 受影响版本:runc < 1.2.8, < 1.3.3, < 1.4.0-rc.3

# 立即更新runc到修复版本
# Debian/Ubuntu
sudo apt update && sudo apt install -y runc

# RHEL/CentOS/Fedora
sudo dnf update -y runc

# 验证更新后的版本
runc --version
# 确保版本 >= 1.2.8 或 >= 1.3.3 或 >= 1.4.0-rc.3

1.3 容器逃逸的常见攻击模式

除了runc漏洞本身,容器逃逸还有几种常见模式。了解它们对建立有效防御至关重要(我在实际工作中见过不少团队踩这些坑):

  • 内核漏洞利用:容器和宿主机共享内核,任何可从普通用户触发的内核漏洞都可能被用来逃逸。dirty pipe(CVE-2022-0847)和各种io_uring漏洞都属于这一类
  • 特权容器滥用:以--privileged运行的容器基本没有隔离可言——它能看到宿主机所有设备,拥有所有capabilities,Seccomp也被禁用。这种做法在生产环境中应该被严格禁止
  • 敏感路径挂载:将//etc、Docker socket(/var/run/docker.sock)等路径挂载到容器中,相当于直接把宿主机的钥匙递了过去
  • CAP_SYS_ADMIN滥用:这个能力几乎等同于root权限,拥有它的容器可以执行挂载操作,进而突破命名空间隔离

第二章:Seccomp——系统调用层的精确过滤

2.1 Seccomp的工作原理

Seccomp(Secure Computing Mode)是Linux内核提供的系统调用过滤机制。它的核心思路其实很简单:Linux内核有超过300个系统调用,但一个典型的应用程序通常只需要其中几十个。为什么要让容器进程能调用它根本用不到的那些呢?Seccomp允许你创建白名单或黑名单,精确控制进程可以使用哪些系统调用。

Seccomp使用BPF(Berkeley Packet Filter)程序来定义过滤规则。每当进程尝试执行系统调用时,内核先把调用号传给BPF程序,由它决定是允许(ALLOW)、返回错误(ERRNO)、记录日志(LOG)还是直接杀死进程(KILL)。

Docker和containerd都自带默认的Seccomp配置文件,会阻止大约44个被认为不必要且可能危险的系统调用,包括:

  • mountumount2:防止容器内的挂载操作
  • ptrace:防止进程跟踪和调试(常被用于代码注入)
  • reboot:防止容器重启宿主机
  • kexec_load:防止加载新内核
  • bpf:防止加载eBPF程序
  • unshare:防止创建新的命名空间

2.2 为容器创建自定义Seccomp配置

默认配置是个不错的起点,但对高安全环境来说还不够。你应该为每个应用创建量身定制的配置——先用审计模式跑一遍,记录应用实际用了哪些系统调用,然后只允许这些。

# 步骤1:使用strace分析应用使用的系统调用
# 以Nginx为例
strace -f -c -S calls nginx -g "daemon off;" 2>&1 | head -50

# 或者使用OCI seccomp-bpf-hook自动生成Seccomp配置文件
# 安装(Fedora/RHEL)
sudo dnf install -y oci-seccomp-bpf-hook

# 运行容器并自动记录系统调用
sudo podman run --annotation io.containers.trace-syscall="of:/tmp/nginx-seccomp.json"     -d --name nginx-trace nginx:latest

# 等待应用正常运行一段时间,执行各种操作以覆盖所有代码路径
sleep 30
curl http://localhost:80

# 停止容器
sudo podman stop nginx-trace

# 查看生成的Seccomp配置文件
cat /tmp/nginx-seccomp.json | jq .

下面是一个为Web服务器定制的Seccomp配置示例。注意看,默认动作是ERRNO(拒绝),只有明确列出的系统调用才会被允许:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "archMap": [
    {
      "architecture": "SCMP_ARCH_X86_64",
      "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]
    }
  ],
  "syscalls": [
    {
      "names": [
        "accept4", "bind", "brk", "clock_gettime",
        "clone", "close", "connect", "dup2",
        "epoll_create1", "epoll_ctl", "epoll_wait",
        "eventfd2", "exit", "exit_group", "fchown",
        "fcntl", "fstat", "futex", "getdents64",
        "getpid", "getuid", "ioctl", "listen",
        "lseek", "madvise", "mmap", "mprotect",
        "munmap", "nanosleep", "newfstatat", "open",
        "openat", "pread64", "read", "recvfrom",
        "recvmsg", "rt_sigaction", "rt_sigprocmask",
        "rt_sigreturn", "sendfile", "sendmsg", "sendto",
        "set_robust_list", "set_tid_address",
        "setgroups", "setgid", "setuid",
        "setsockopt", "shutdown", "sigaltstack",
        "socket", "socketpair", "stat", "sysinfo",
        "uname", "wait4", "write", "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    },
    {
      "names": ["prctl"],
      "action": "SCMP_ACT_ALLOW",
      "args": [
        { "index": 0, "value": 38, "op": "SCMP_CMP_EQ" }
      ]
    }
  ]
}
# 使用自定义Seccomp配置运行Docker容器
docker run -d   --security-opt seccomp=/path/to/custom-seccomp.json   --name secure-nginx   nginx:latest

# 使用Podman运行
podman run -d   --security-opt seccomp=/path/to/custom-seccomp.json   --name secure-nginx   nginx:latest

2.3 Kubernetes中的Seccomp配置

从Kubernetes 1.27开始,Seccomp配置通过Pod的securityContext字段来设置。你可以用内置的RuntimeDefault配置(推荐至少用这个),也可以指定自定义配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: secure-web-app
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: web
    image: nginx:latest
    securityContext:
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      runAsUser: 1000
      capabilities:
        drop:
          - ALL
        add:
          - NET_BIND_SERVICE
---
# 使用自定义Seccomp配置文件
# 配置文件需要放在节点的 /var/lib/kubelet/seccomp/ 目录下
apiVersion: v1
kind: Pod
metadata:
  name: custom-seccomp-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/nginx-custom.json
  containers:
  - name: web
    image: nginx:latest

如果你管理的是一个大规模集群,手动分发Seccomp配置文件到每个节点显然不太现实。这时候可以用Security Profiles Operator来集中管理:

# 安装Security Profiles Operator
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/security-profiles-operator/main/deploy/operator.yaml

# 创建SeccompProfile资源
apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
  name: nginx-strict
  namespace: default
spec:
  defaultAction: SCMP_ACT_ERRNO
  syscalls:
  - action: SCMP_ACT_ALLOW
    names:
    - accept4
    - bind
    - clone
    - close
    - connect
    - epoll_create1
    - epoll_ctl
    - epoll_wait
    - exit
    - exit_group
    - fcntl
    - fstat
    - futex
    - getdents64
    - getpid
    - listen
    - mmap
    - mprotect
    - munmap
    - nanosleep
    - newfstatat
    - openat
    - read
    - recvfrom
    - rt_sigaction
    - rt_sigprocmask
    - sendfile
    - sendto
    - setsockopt
    - socket
    - write
    - writev

第三章:AppArmor与SELinux——强制访问控制的容器应用

3.1 AppArmor容器安全配置

AppArmor是基于路径的强制访问控制(MAC)系统,在Ubuntu和Debian系发行版中默认启用。跟Seccomp不同的是,AppArmor不仅能控制系统调用,还能精确限制进程对文件系统、网络和Linux capabilities的访问——控制粒度更细。

Docker和containerd默认会为容器加载docker-default(或cri-containerd.apparmor.d)配置文件,提供基础保护。但对于生产环境,你真的应该为每个应用写专门的配置。

# /etc/apparmor.d/containers/nginx-hardened
#include <tunables/global>

profile nginx-hardened flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # 网络访问控制
  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  # 拒绝原始套接字(防止网络嗅探)
  deny network raw,
  deny network packet,

  # Nginx需要的文件访问权限
  /usr/sbin/nginx mr,
  /etc/nginx/** r,
  /var/log/nginx/** rw,
  /var/cache/nginx/** rw,
  /run/nginx.pid rw,
  /var/www/html/** r,

  # 共享库
  /lib/** mr,
  /usr/lib/** mr,

  # 临时文件
  /tmp/** rw,
  owner /proc/*/fd/ r,
  owner /proc/*/fdinfo/* r,

  # 明确拒绝的操作
  deny /proc/sys/** w,          # 防止修改内核参数
  deny /sys/** w,               # 防止修改sysfs
  deny /etc/shadow r,           # 防止读取密码哈希
  deny /etc/ssh/** rw,          # 防止访问SSH配置和密钥
  deny /root/** rw,             # 防止访问root家目录

  # 拒绝危险的capabilities
  deny capability sys_admin,
  deny capability sys_ptrace,
  deny capability sys_rawio,
  deny capability sys_module,
  deny capability dac_override,

  # 允许需要的capabilities
  capability net_bind_service,   # 绑定80/443端口
  capability setuid,             # worker进程降权
  capability setgid,
  capability chown,
  capability dac_read_search,
}
# 加载AppArmor配置文件
sudo apparmor_parser -r /etc/apparmor.d/containers/nginx-hardened

# 使用自定义AppArmor配置运行Docker容器
docker run -d   --security-opt apparmor=nginx-hardened   --name secure-nginx   nginx:latest

# 验证AppArmor配置是否生效
docker inspect secure-nginx | grep -i apparmor
# 应显示 "AppArmorProfile": "nginx-hardened"

# 查看AppArmor拒绝日志
sudo dmesg | grep -i apparmor | tail -20
# 或
sudo journalctl -k | grep -i apparmor

3.2 SELinux容器安全配置

在RHEL、CentOS和Fedora系统上,SELinux是默认的强制访问控制系统。相比AppArmor,SELinux基于标签(label)而非路径,提供了更细粒度的访问控制,不过配置复杂度也更高(这是很多人对它又爱又恨的原因)。

Podman和CRI-O在RHEL系系统上默认启用SELinux容器隔离。容器进程被标记为container_t类型,容器文件被标记为container_file_t类型,SELinux策略确保容器只能访问正确标记的资源。

# 验证SELinux是否处于强制模式
getenforce
# 应输出: Enforcing

# 查看容器进程的SELinux上下文
podman run -d --name test-nginx nginx:latest
ps -eZ | grep nginx
# 输出类似: system_u:system_r:container_t:s0:c123,c456 ... nginx

# 使用自定义SELinux标签运行容器
podman run -d   --security-opt label=type:svirt_apache_t   --name selinux-nginx   nginx:latest

# 查看SELinux拒绝日志
sudo ausearch -m AVC -ts recent | grep container
# 或使用sealert分析
sudo sealert -a /var/log/audit/audit.log

对于需要自定义SELinux策略的场景,有个好用的工具叫udica,它能根据运行中的容器自动生成策略:

# 安装udica
sudo dnf install -y udica

# 先运行容器(不带自定义策略)
podman run -d --name my-app -v /data:/app/data:Z my-app:latest

# 使用udica基于运行中的容器自动生成SELinux策略
podman inspect my-app | sudo udica my-app-policy

# 加载生成的策略
sudo semodule -i my-app-policy.cil /usr/share/udica/templates/base_container.cil

# 使用新策略重新运行容器
podman stop my-app && podman rm my-app
podman run -d --name my-app   --security-opt label=type:my-app-policy.process   -v /data:/app/data:Z   my-app:latest

3.3 Kubernetes中的AppArmor配置

好消息是,Kubernetes从1.30版本开始,AppArmor支持已经升级为GA(正式可用)了。不需要再用注解那种别扭的方式了,直接在Pod的securityContext中指定就行:

apiVersion: v1
kind: Pod
metadata:
  name: apparmor-nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    securityContext:
      appArmorProfile:
        type: Localhost
        localhostProfile: nginx-hardened
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      runAsUser: 101
      capabilities:
        drop:
          - ALL
        add:
          - NET_BIND_SERVICE

第四章:无根容器——从架构上消除特权提升风险

4.1 为什么无根容器是更好的默认选择

传统的Docker架构依赖一个以root权限运行的守护进程(dockerd)来管理所有容器。想想这意味着什么——如果守护进程被攻破,攻击者就直接拿到了宿主机的root权限。即便容器本身不以root运行,Docker守护进程的root权限仍然是一个巨大的攻击面。

无根容器(rootless container)从架构层面解决了这个问题。

整个容器引擎——包括运行时、存储和网络——都以普通用户身份运行。容器内部的"root"用户实际上映射到宿主机上的一个无特权用户ID。即使攻击者成功逃逸出容器,拿到的也只是宿主机上一个普通用户的权限,而不是root。这个区别是巨大的。

Podman天生就是为无根模式设计的——它没有守护进程,每个容器都是调用用户的子进程。Docker从20.10版本开始也支持无根模式,但需要额外配置。2026年的趋势非常明确:无根容器应该成为内部和多租户工作负载的默认选择

4.2 Podman无根容器配置实战

# 安装Podman(大多数现代发行版已预装)
# Fedora/RHEL
sudo dnf install -y podman

# Ubuntu 22.04+
sudo apt install -y podman

# 验证无根模式配置
# 检查用户命名空间映射
cat /etc/subuid
# 输出格式: username:起始UID:范围
# 例如: admin:100000:65536

cat /etc/subgid
# 输出格式: username:起始GID:范围

# 如果映射不存在,手动添加
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER

# 以普通用户身份运行容器(无需sudo)
podman run -d --name rootless-nginx -p 8080:80 nginx:latest

# 验证容器以非root身份运行
podman top rootless-nginx user,huser
# USER    HUSER
# root    admin    <-- 容器内的root映射到宿主机的admin用户

Podman的无根模式默认只赋予容器11个capabilities(而Docker是14个),攻击面更小。但说实话,你还应该更进一步——只保留应用实际需要的capabilities:

# 以最小capabilities运行容器
podman run -d   --cap-drop=ALL   --cap-add=NET_BIND_SERVICE   --read-only   --tmpfs /tmp:rw,noexec,nosuid   --tmpfs /var/cache/nginx:rw,noexec,nosuid   --tmpfs /run:rw,noexec,nosuid   --security-opt no-new-privileges:true   --name hardened-nginx   -p 8080:80   nginx:latest

# 解释各参数:
# --cap-drop=ALL          : 移除所有capabilities
# --cap-add=NET_BIND_SERVICE : 只添加绑定低端口的能力
# --read-only             : 根文件系统只读
# --tmpfs /tmp            : 只在必要目录提供可写tmpfs
# --no-new-privileges     : 禁止通过setuid/setgid提升权限

4.3 Docker无根模式配置

如果你的环境还在用Docker(说实话,很多公司都是),也可以启用无根模式:

# 安装Docker无根模式依赖
sudo apt install -y uidmap dbus-user-session

# 以普通用户身份安装Docker无根模式
dockerd-rootless-setuptool.sh install

# 设置环境变量(添加到~/.bashrc)
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# 启动无根Docker服务
systemctl --user start docker
systemctl --user enable docker

# 验证无根模式
docker context ls
# 应显示 rootless 上下文

docker info | grep -i "root"
# rootless: true

第五章:Kubernetes Pod安全标准——集群级安全策略

5.1 Pod Security Standards(PSS)三级安全模型

Kubernetes Pod安全标准定义了三个安全级别,从宽松到严格递进:

  • Privileged(特权):完全不受限制,允许已知的特权提升。适用于系统级基础设施工作负载,比如CNI插件、日志收集器这类
  • Baseline(基线):最低限度的限制,阻止已知的特权提升手段。禁止特权容器、hostPID/hostIPC/hostNetwork,限制危险的volume类型和capabilities
  • Restricted(受限):严格限制的最佳实践。在Baseline基础上,还要求以非root运行、丢弃所有capabilities、设置只读根文件系统等。这是生产工作负载应该达到的标准

Pod Security Admission(PSA)控制器是Kubernetes内置的准入控制器,通过命名空间标签来强制执行这些标准。支持三种操作模式:

  • enforce:违规Pod直接被拒绝创建
  • audit:违规行为记录到审计日志,但不阻止创建
  • warn:触发用户可见的警告,但不阻止创建

5.2 实施Pod安全标准的最佳实践

一个经验之谈:别一上来就开enforce。推荐的渐进式策略是——先用warn和audit模式发现违规,把问题都修完了,再切换到enforce模式。这样可以避免突然搞挂生产环境的服务。

# 步骤1:为命名空间添加warn和audit标签
kubectl label namespace production   pod-security.kubernetes.io/warn=restricted   pod-security.kubernetes.io/warn-version=latest   pod-security.kubernetes.io/audit=restricted   pod-security.kubernetes.io/audit-version=latest

# 部署应用,观察警告信息
kubectl -n production apply -f deployment.yaml
# Warning: would violate PodSecurity "restricted:latest":
#   allowPrivilegeEscalation != false
#   unrestricted capabilities
#   runAsNonRoot != true

# 步骤2:修复所有违规项后,启用enforce模式
kubectl label namespace production   pod-security.kubernetes.io/enforce=restricted   pod-security.kubernetes.io/enforce-version=latest

# 验证标签
kubectl get namespace production -o yaml | grep pod-security

下面给出一个完整的、符合Restricted级别要求的Deployment示例,可以直接拿来参考:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-web-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      # Pod级别安全上下文
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      automountServiceAccountToken: false
      containers:
      - name: web
        image: my-registry/web-app:v1.2.3@sha256:abc123def456
        ports:
        - containerPort: 8080
          protocol: TCP
        # 容器级别安全上下文
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
              - ALL
        resources:
          limits:
            cpu: "500m"
            memory: "256Mi"
          requests:
            cpu: "100m"
            memory: "128Mi"
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /var/cache
      volumes:
      - name: tmp
        emptyDir:
          sizeLimit: 100Mi
      - name: cache
        emptyDir:
          sizeLimit: 200Mi

5.3 使用NetworkPolicy加固Pod间通信

Pod安全标准管的是Pod本身的安全配置,但Pod之间的网络通信同样需要管控。默认情况下,Kubernetes集群内所有Pod之间可以自由通信——这在横向移动攻击中非常危险。说白了,攻击者拿下一个Pod后,可以毫无阻碍地探测和攻击集群中的其他服务。

NetworkPolicy提供了集群内的网络微分段能力:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: web-app-network-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  - Egress
  ingress:
  # 只允许来自Ingress Controller的流量
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx
      podSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  egress:
  # 只允许访问数据库
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  # 允许DNS查询
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
---
# 默认拒绝所有流量的基础策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

第六章:容器镜像安全——构建阶段的安全左移

6.1 镜像扫描与漏洞管理

安全不能只在运行时做——"安全左移"意味着在CI/CD流水线的构建阶段就发现并修复安全问题。有研究数据显示,超过85%的生产环境容器镜像包含高危或严重漏洞。这个数字确实触目惊心,但用Trivy这类工具做自动化镜像扫描可以显著改善这一状况:

# 安装Trivy
sudo apt install -y trivy
# 或
brew install trivy

# 扫描容器镜像中的漏洞
trivy image nginx:latest

# 只显示高危和严重漏洞
trivy image --severity HIGH,CRITICAL nginx:latest

# 扫描时同时检查配置文件的安全问题
trivy image --scanners vuln,misconfig nginx:latest

# 在CI/CD中使用:发现严重漏洞时让构建失败
trivy image --exit-code 1 --severity CRITICAL my-app:latest

# 扫描Dockerfile中的安全问题
trivy config --file-patterns "Dockerfile" .

# 生成SBOM(软件物料清单)
trivy image --format spdx-json --output sbom.json my-app:latest

6.2 安全的Dockerfile编写原则

一个安全的Dockerfile应该遵循最小化原则——只包含应用运行所需的最少文件和依赖,多余的东西一概不要:

# 使用多阶段构建,最小化最终镜像
FROM golang:1.23-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server

# 使用distroless基础镜像(没有shell、没有包管理器)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
# 使用非root用户
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]

几个关键的安全原则,请务必记住:

  • 使用特定版本标签:永远别用latest标签,用精确的版本号或摘要(@sha256:...)来确保可重复性和可审计性
  • 多阶段构建:编译工具链和源代码不应该出现在最终镜像中,它们只会增加攻击面
  • Distroless或Alpine:使用最小基础镜像。没有shell的镜像意味着攻击者即使进了容器也很难操作
  • 非root用户:在Dockerfile中用USER指令切换到非root用户
  • 不存储敏感信息:千万不要在镜像中硬编码密钥、密码或API令牌——用secrets管理方案来处理

第七章:纵深防御检查清单与自动化审计

7.1 容器安全纵深防御检查清单

前面讲了这么多,现在把所有内容整合成一个可操作的检查清单。建议你把它打印出来贴在工位旁边(或者收藏到书签里也行):

运行时层(Runtime Layer):

  • runc更新到最新修复版本(>= 1.2.8 / 1.3.3 / 1.4.0-rc.3)
  • Seccomp配置文件已应用(至少RuntimeDefault,推荐自定义配置)
  • AppArmor或SELinux处于强制模式
  • 无根容器模式已启用
  • --no-new-privileges标志已设置
  • capabilities已最小化(drop ALL,只add必需的)

Kubernetes集群层(Cluster Layer):

  • Pod Security Standards已配置为Restricted级别
  • NetworkPolicy默认拒绝已应用
  • RBAC最小权限原则已实施
  • API Server审计日志已启用
  • automountServiceAccountToken设为false

镜像与构建层(Image/Build Layer):

  • 使用最小基础镜像(distroless/Alpine)
  • 镜像漏洞扫描集成到CI/CD流水线
  • 镜像签名验证已启用
  • 使用摘要而非标签引用镜像
  • SBOM生成并存档

7.2 自动化安全审计脚本

最后分享一个实用的综合审计脚本,可以快速检查Docker/Podman主机的容器安全状态。虽然它不能替代专业的安全评估工具,但作为日常巡检来说够用了:

#!/bin/bash
# container-security-audit.sh - 容器安全审计脚本
# 用法: sudo bash container-security-audit.sh

set -euo pipefail

RED="[0;31m"
GREEN="[0;32m"
YELLOW="[1;33m"
NC="[0m"

echo "======================================"
echo "  容器安全审计 - $(date +%Y-%m-%d)"
echo "======================================"
echo ""

# 检查runc版本
echo "[*] 检查runc版本..."
if command -v runc &>/dev/null; then
    RUNC_VER=$(runc --version | head -1 | awk '{print $3}')
    echo -e "    runc版本: $RUNC_VER"
    echo -e "    ${YELLOW}请确认版本 >= 1.2.8 或 >= 1.3.3${NC}"
else
    echo -e "    ${RED}[FAIL] runc未安装${NC}"
fi

echo ""

# 检查运行中的特权容器
echo "[*] 检查特权容器..."
RUNTIME="docker"
command -v podman &>/dev/null && RUNTIME="podman"

PRIV_COUNT=$($RUNTIME ps -q 2>/dev/null | wc -l)
echo "    运行中的容器数量: $PRIV_COUNT"

echo ""

# 检查SELinux/AppArmor状态
echo "[*] 检查强制访问控制..."
if command -v getenforce &>/dev/null; then
    SELINUX_STATUS=$(getenforce)
    if [ "$SELINUX_STATUS" = "Enforcing" ]; then
        echo -e "    ${GREEN}[PASS] SELinux: Enforcing${NC}"
    else
        echo -e "    ${RED}[FAIL] SELinux: $SELINUX_STATUS${NC}"
    fi
elif command -v aa-status &>/dev/null; then
    echo -e "    ${GREEN}[INFO] AppArmor已安装${NC}"
else
    echo -e "    ${RED}[FAIL] 未检测到SELinux或AppArmor${NC}"
fi

echo ""
echo "======================================"
echo "  审计完成"
echo "======================================"

总结:构建容器安全的纵深防线

容器安全不是一个单点问题,而是一个系统工程。从2025年底的runc容器逃逸漏洞到日益复杂的供应链攻击,威胁形势一直在变化。但好消息是,Linux内核和容器生态提供了丰富的安全工具和机制——关键在于你愿不愿意花时间把它们组合成一套层层递进的防御体系。

纵深防御的核心思路说白了就是:不要把鸡蛋放在一个篮子里。Seccomp限制系统调用、AppArmor/SELinux控制资源访问、无根容器消除特权提升风险、Pod Security Standards在集群层面强制执行安全策略、NetworkPolicy实现网络微分段、镜像扫描在构建阶段发现漏洞——每一层都在弥补其他层可能存在的缺口。

我的实施建议:

  1. 立即行动:更新runc到最新版本,确保SELinux/AppArmor处于强制模式。这两件事今天就可以做
  2. 短期目标:为所有容器启用Seccomp RuntimeDefault配置,开始使用Podman无根模式
  3. 中期目标:为关键应用创建自定义Seccomp和AppArmor配置,在Kubernetes中实施Restricted级别的Pod安全标准
  4. 长期目标:建立完整的容器安全CI/CD流水线,包括镜像扫描、签名验证、SBOM管理和运行时监控

最后想说的是,安全加固是一个持续的过程,没有"做完了"的那一天。容器技术在不断演进,威胁也在不断变化。定期审计你的安全配置,关注新的漏洞公告,持续优化防御策略——这才是真正的安全运营之道。

关于作者 Tobias Lindqvist

Tobias spent six years on Canonical's kernel team in the LTS hardening group, mostly working on Livepatch tooling and the long tail of CVE backports nobody wants to do. He left in 2025 to consult independently with Nordic infrastructure customers running large Ubuntu fleets, where most of the work is AppArmor profiles, unattended-upgrades that don't break things, and explaining what kernel.unprivileged_userns_clone actually does. He co-maintains a small open-source tool for diffing kernel config across distributions and contributes occasionally to the linux-hardening mailing list. His side project is a Yocto-based minimal image for industrial gateways that boots in under four seconds with full secure boot. Tobias writes mostly about kernel-level security primitives - namespaces, seccomp, LSMs - with the assumption that you've already read the man page.