引言:容器安全——你的隔离真的可靠吗?
容器技术已经成了现代基础设施的基石,这一点没人会否认。从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利用的恰恰是这个掩码过程中的竞态窗口:
- 攻击者在容器镜像中预置一个恶意的init进程
- runc启动容器并开始设置masked paths
- 在runc完成掩码挂载之前(这个时间窗口虽然很短,但足够了),恶意进程通过符号链接将挂载目标重定向到
/proc/self/exe——也就是runc二进制本身 - runc最终把可写挂载绑定到了错误的目标上,攻击者获得了对runc二进制的写权限
- 攻击者覆写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个被认为不必要且可能危险的系统调用,包括:
mount、umount2:防止容器内的挂载操作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实现网络微分段、镜像扫描在构建阶段发现漏洞——每一层都在弥补其他层可能存在的缺口。
我的实施建议:
- 立即行动:更新runc到最新版本,确保SELinux/AppArmor处于强制模式。这两件事今天就可以做
- 短期目标:为所有容器启用Seccomp RuntimeDefault配置,开始使用Podman无根模式
- 中期目标:为关键应用创建自定义Seccomp和AppArmor配置,在Kubernetes中实施Restricted级别的Pod安全标准
- 长期目标:建立完整的容器安全CI/CD流水线,包括镜像扫描、签名验证、SBOM管理和运行时监控
最后想说的是,安全加固是一个持续的过程,没有"做完了"的那一天。容器技术在不断演进,威胁也在不断变化。定期审计你的安全配置,关注新的漏洞公告,持续优化防御策略——这才是真正的安全运营之道。