容器的安全模型是纵深防御——Namespace 提供第一层隔离,Capabilities 限制特权操作,seccomp 过滤系统调用,AppArmor 控制文件访问。这四层防护叠加在一起,构成了容器的安全边界。
但这个边界并非无懈可击。特权容器可以访问宿主内核的所有系统调用,内核漏洞可以突破 Namespace 隔离,配置不当的 seccomp 规则形同虚设。理解每一层安全机制的原理和局限,是构建安全容器的基础。
容器安全的历史是一部”事故驱动”的演进史。容器早期默认以 root 身份运行,Capabilities 几乎不做限制,seccomp 和 AppArmor 更是无人问津——直到 CVE-2019-5736 爆发。这个漏洞允许攻击者通过 /proc/self/exe 符号链接覆盖宿主机上的 runc 二进制文件,实现容器逃逸。此后,CVE-2022-0185(内核 Cgroup v1 逃逸)、CVE-2024-1086(内核 netfilter UAF 逃逸)等漏洞相继出现,每一次逃逸事件都推动了安全机制的加固:Capabilities 从”全部授予”变为”最小子集”,seccomp 从”可选”变为”默认启用”,rootless 容器从”实验性”变为”生产可用”。理解这段历史,你就会明白为什么容器安全配置如此复杂——每一个限制都是对真实攻击的回应。
前置知识
- Ch02 Linux Namespace 深入:Namespace 是容器安全的第一层防线,理解 Namespace 的隔离边界是理解安全加固的前提
- Ch03 Cgroup v2 深入:Cgroup 是资源限制的第二层防线,防止容器耗尽宿主资源
- Linux 安全模块基础:DAC(自主访问控制)、MAC(强制访问控制)、Capabilities 机制
容器安全是一个持续演进的话题。本章讨论的安全机制基于当前(2026 年)的最佳实践,新的 CVE 和防御手段会不断出现。
一、容器安全模型
1.1 纵深防御层级
1.2 容器 vs 虚拟机的安全边界
| 安全维度 | 容器 | 虚拟机 |
|---|---|---|
| 内核共享 | 共享宿主内核 | 独立内核 |
| 内核漏洞影响 | 影响所有容器 | 只影响一个 VM |
| 攻击面 | 系统调用接口 | 虚拟硬件接口 |
| 逃逸难度 | 较低(内核漏洞) | 较高(需突破 VMM) |
| 安全模块 | seccomp/AppArmor/SELinux | 硬件虚拟化扩展 |
容器共享宿主内核意味着:一个内核漏洞可能导致所有容器被逃逸。这就是为什么 gVisor 和 Kata Containers 选择不共享内核——详见 Ch11 沙箱运行时。
二、Linux Capabilities
2.1 从 root 到 Capabilities
传统 UNIX 模型只有 root 和非 root 两种权限,过于粗粒度。Linux Capabilities 将 root 权限拆分为 40+ 个细粒度能力:
# 查看所有 Capabilitiescapsh --print
# 查看当前进程的 Capabilitiescat /proc/self/status | grep Cap# CapInh: 0000000000000000# CapPrm: 0000003fffffffff# CapEff: 0000003fffffffff# CapBnd: 0000003fffffffff# CapAmb: 0000000000000000
# 解码 Capabilitiespython3 -c "caps = 0x3ffffffffffor i in range(40): if caps & (1 << i): print(f'CAP_{i}')"2.2 容器相关的 Capabilities
| Capability | 功能 | 容器默认 | 风险等级 |
|---|---|---|---|
| CAP_AUDIT_WRITE | 写审计日志 | 低 | |
| CAP_KILL | 发送信号 | 低 | |
| CAP_NET_BIND_SERVICE | 绑定特权端口 | 低 | |
| CAP_CHOWN | 修改文件所有者 | 中 | |
| CAP_DAC_OVERRIDE | 绕过文件权限检查 | 中 | |
| CAP_FOWNER | 绕过文件所有者检查 | 中 | |
| CAP_SETFCAP | 设置文件 Capabilities | 高 | |
| CAP_SYS_ADMIN | 系统管理操作 | 极高 | |
| CAP_SYS_PTRACE | 追踪进程 | 极高 | |
| CAP_NET_ADMIN | 网络管理 | 高 | |
| CAP_SYS_MODULE | 加载内核模块 | 极高 |
2.3 Docker 的默认 Capabilities
# 查看 Docker 容器的默认 Capabilitiesdocker run --rm alpine capsh --print | grep "Current:"
# Docker 默认授予的 Capabilities:# CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE,# CAP_FOWNER, CAP_FSETID, CAP_KILL,# CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_RAW,# CAP_SETGID, CAP_SETUID, CAP_SETFCAP,# CAP_SETPCAP, CAP_SYS_CHROOT
# 添加/删除 Capabilitiesdocker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginxdocker run --cap-add=SYS_ADMIN alpine # 危险!2.4 Capabilities 在 runc 中的实现
// runc 设置 Capabilities(简化)func setupCapabilities(spec *specs.LinuxCapabilities) error { // 1. 清除所有 Capabilities if err := unix.Prctl(unix.PR_CAPBSET_DROP, 0, 0, 0, 0); err != nil { return err }
// 2. 设置 Bounding set(上限) for _, cap := range spec.Bounding { if err := unix.Prctl(unix.PR_CAPBSET_READ, uintptr(cap), 0, 0, 0); err != nil { return err } }
// 3. 设置 Effective set(当前生效) caps := &unix.CapUser{ Header: unix.CapUserHeader{Version: unix.LINUX_CAPABILITY_VERSION_3}, Data: unix.CapUserData{}, } for _, cap := range spec.Effective { caps.Data[cap/32].Effective |= 1 << (cap % 32) }
if err := unix.Capset(caps.Header, caps.Data[:]); err != nil { return err }
return nil}三、seccomp
3.1 seccomp 原理
seccomp(Secure Computing Mode)限制进程可以调用的系统调用。它使用 BPF(Berkeley Packet Filter)程序在内核层面过滤系统调用:
3.2 Docker 的默认 seccomp 配置
Docker 默认使用 seccomp 配置文件,禁止约 44 个危险系统调用:
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": ["accept", "access", "arch_prctl", "bind", "brk", ...], "action": "SCMP_ACT_ALLOW" }, { "names": ["keyctl", "add_key", "request_key"], "action": "SCMP_ACT_ERRNO" }, { "names": ["mount"], "action": "SCMP_ACT_ERRNO", "args": [] } ]}3.3 被默认 seccomp 禁止的系统调用
| 系统调用 | 原因 | 风险 |
|---|---|---|
keyctl | 内核密钥环操作 | 内核漏洞利用 |
add_key | 添加密钥 | 内核漏洞利用 |
request_key | 请求密钥 | 内核漏洞利用 |
mount | 挂载文件系统 | 文件系统逃逸 |
umount2 | 卸载文件系统 | 文件系统逃逸 |
pivot_root | 切换根目录 | 文件系统逃逸 |
bpf | 加载 BPF 程序 | 内核代码执行 |
perf_event_open | 性能监控 | 信息泄露 |
kcmp | 比较进程资源 | 信息泄露 |
userfaultfd | 用户态页错误处理 | 内核漏洞利用 |
3.4 自定义 seccomp 配置
# 使用自定义 seccomp 配置docker run --security-opt seccomp=/path/to/seccomp.json nginx
# 禁用 seccomp(危险!)docker run --security-opt seccomp=unconfined nginx
# 使用 Docker 默认 seccomp 配置作为模板wget https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json3.5 用 seccomp-tools 分析
# 安装 seccomp-toolsgem install seccomp-tools
# 分析容器的系统调用seccomp-tools dump -c "docker run alpine ls" | head -30
# 追踪特定系统调用seccomp-tools trace -c "docker run alpine cat /etc/passwd"四、AppArmor
4.1 AppArmor 原理
AppArmor 是 Linux 的强制访问控制(MAC)系统,基于路径定义文件访问规则:
# 查看 AppArmor 状态sudo aa-status
# 查看容器使用的 AppArmor 配置docker inspect mycontainer --format '{{.AppArmorProfile}}'
# Docker 默认使用 docker-default 配置4.2 Docker 的默认 AppArmor 配置
# 查看 docker-default 配置sudo cat /etc/apparmor.d/docker-default
# 关键规则(简化):# deny /sys/[^f]/** wklx, # 禁止写 /sys(除 /sys/fs)# deny /sys/f[^s]/** wklx, # 禁止写 /sys/f(除 /sys/fs/cgroup)# deny /sys/fs/[^c]/** wklx, # 禁止写 /sys/fs(除 /sys/fs/cgroup)# owner /** rw, # 允许读写自己拥有的文件# /proc/** r, # 允许读 /proc# /dev/** rw, # 允许读写 /dev4.3 自定义 AppArmor 配置
# 创建自定义 AppArmor 配置sudo cat > /etc/apparmor.d/mycontainer << 'EOF'#include <tunables/global>
profile mycontainer flags=(attach_disconnected,mediate_deleted) { #include <abstractions/base>
# 允许读 /etc /etc/** r,
# 允许写 /tmp /tmp/** rw,
# 禁止读 /etc/shadow deny /etc/shadow r,
# 允许网络连接 network inet tcp, network inet udp,
# 允许信号 signal (receive) peer=/usr/bin/docker,}EOF
# 加载配置sudo apparmor_parser -r /etc/apparmor.d/mycontainer
# 使用自定义配置docker run --security-opt apparmor=mycontainer nginx五、Rootless 容器
5.1 Rootless 容器原理
Rootless 容器利用 User Namespace 将容器内的 root 映射到宿主机的普通用户,无需 root 权限即可运行容器:
5.2 Podman 的 Rootless 模式
# Podman 天然支持 rootlesspodman run -d nginx
# 底层原理:# 1. 创建 User Namespace# 2. 配置 UID 映射:0 → 100000, 1 → 100001, ...# 3. 在 User Namespace 内创建其他 Namespace# 4. 使用 fuse-overlayfs 替代 OverlayFS(无需 root)# 5. 使用 slirp4netns 或 pasta 替代 veth pair(无需 root)5.3 Docker 的 Rootless 模式
# 安装 Docker rootless 模式dockerd-rootless-setuptool.sh install
# 运行 rootless 容器docker run -d nginx
# 限制:# - 不能绑定特权端口(< 1024)# - 网络性能较低(slirp4netns)# - 不能使用 AppArmor# - 不能修改 sysctl 参数5.4 Rootless 容器的技术栈
| 组件 | 传统容器 | Rootless 容器 |
|---|---|---|
| 运行时 | runc | runc (rootless mode) |
| 文件系统 | OverlayFS | fuse-overlayfs |
| 网络 | veth + bridge | slirp4netns / pasta |
| Cgroup | Cgroup v2 (root) | Cgroup v2 (delegation) |
| UID 映射 | 无 | User Namespace |
六、安全基线与最佳实践
6.1 容器安全检查清单
| 检查项 | 安全配置 | 风险等级 |
|---|---|---|
| 特权容器 | 不使用 —privileged | 极高 |
| Capabilities | —cap-drop=ALL + 按需添加 | 高 |
| seccomp | 使用默认或自定义配置 | 高 |
| AppArmor | 使用默认或自定义配置 | 中 |
| 只读文件系统 | —read-only | 中 |
| PID 限制 | —pids-limit=100 | 中 |
| 内存限制 | —memory=512m | 中 |
| User Namespace | rootless 模式 | 高 |
| 镜像签名 | Docker Content Trust | 中 |
| 镜像扫描 | Trivy/Snyk/Grype | 中 |
6.2 安全配置示例
# Kubernetes Pod 安全上下文apiVersion: v1kind: Podspec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 seccompProfile: type: RuntimeDefault containers: - name: nginx securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] add: ["NET_BIND_SERVICE"] resources: limits: memory: "512Mi" cpu: "1"6.3 容器安全扫描
# 使用 Trivy 扫描镜像漏洞trivy image nginx:latest
# 输出示例:# nginx:latest (debian 12.4)# ===========================# Total: 42 (UNKNOWN: 0, LOW: 15, MEDIUM: 20, HIGH: 6, CRITICAL: 1)# ┌──────────────┬────────────┬──────────┬───────────────────┐# │ Library │ Vulnerability│ Severity │ Installed Version │# ├──────────────┼────────────┼──────────┼───────────────────┤# │ libc-bin │ CVE-2023-1234│ CRITICAL │ 2.36-9 │# │ libssl3 │ CVE-2023-5678│ HIGH │ 3.0.11 │# └──────────────┴────────────┴──────────┴───────────────────┘七、动手实践
7.1 容器安全审计脚本
#!/bin/bash# 容器安全审计脚本
CONTAINER=$1
echo "=== 容器安全审计: $CONTAINER ==="
echo ""echo "1. 特权模式检查"PRIVILEGED=$(docker inspect -f '{{.HostConfig.Privileged}}' $CONTAINER)if [ "$PRIVILEGED" = "true" ]; then echo " 容器运行在特权模式!"else echo " 容器未运行在特权模式"fi
echo ""echo "2. Capabilities 检查"CAPS=$(docker inspect -f '{{.HostConfig.CapAdd}}' $CONTAINER)echo " 添加的 Capabilities: $CAPS"if echo "$CAPS" | grep -q "SYS_ADMIN"; then echo " CAP_SYS_ADMIN 已添加,风险极高"fi
echo ""echo "3. seccomp 检查"SECCOMP=$(docker inspect -f '{{.HostConfig.SecurityOpt}}' $CONTAINER)if echo "$SECCOMP" | grep -q "unconfined"; then echo " seccomp 已禁用!"else echo " seccomp 已启用"fi
echo ""echo "4. AppArmor 检查"APPARMOR=$(docker inspect -f '{{.AppArmorProfile}}' $CONTAINER)if [ "$APPARMOR" = "" ] || [ "$APPARMOR" = "unconfined" ]; then echo " AppArmor 未配置"else echo " AppArmor: $APPARMOR"fi
echo ""echo "5. 资源限制检查"MEM=$(docker inspect -f '{{.HostConfig.Memory}}' $CONTAINER)CPU=$(docker inspect -f '{{.HostConfig.NanoCpus}}' $CONTAINER)PIDS=$(docker inspect -f '{{.HostConfig.PidsLimit}}' $CONTAINER)echo " 内存限制: $MEM"echo " CPU 限制: $CPU"echo " PID 限制: $PIDS"
echo ""echo "6. 只读文件系统检查"RO=$(docker inspect -f '{{.HostConfig.ReadonlyRootfs}}' $CONTAINER)if [ "$RO" = "true" ]; then echo " 根文件系统为只读"else echo " 根文件系统可写"fi
echo ""echo "7. 挂载检查"MOUNTS=$(docker inspect -f '{{.Mounts}}' $CONTAINER)echo " 挂载点: $MOUNTS"if echo "$MOUNTS" | grep -q "/var/run/docker.sock"; then echo " 挂载了 Docker socket,可逃逸到宿主机!"fi附、实践:容器安全审计
本节用真实命令审计容器的安全配置,包括 Capabilities、Seccomp、只读文件系统等。需要 Docker 环境和 root 权限。
附.1 默认容器的 Capabilities
# 启动一个默认配置的容器docker run -d --name sec-demo alpine:latest sleep 300
# 查看容器进程的实际 CapabilitiesCONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' sec-demo)cat /proc/$CONTAINER_PID/status | grep -i CapCapInh: 0000000000000000CapPrm: 00000000a80425fbCapEff: 00000000a80425fbCapBnd: 00000000a80425fbCapAmb: 0000000000000000这个十六进制掩码 a80425fb 对应 Docker 默认授予的约 14 个 Capabilities(包括 CAP_NET_BIND_SERVICE、CAP_KILL、CAP_SETUID 等)。虽然比 root 的全部 Capabilities 少很多,但仍然包含潜在风险。
附.2 最小权限容器:丢弃所有 Capabilities
docker run --rm --cap-drop=ALL alpine:latest sh -c 'cat /proc/self/status | grep Cap'CapInh: 0000000000000000CapPrm: 0000000000000000CapEff: 0000000000000000CapBnd: 0000000000000000CapAmb: 0000000000000000所有 Capabilities 为 0——容器进程没有任何特权操作能力。这是最安全的配置,但可能导致某些应用无法正常运行(如 ping 需要 CAP_NET_RAW)。
附.3 只读根文件系统
docker run --rm --read-only alpine:latest sh -c \ 'echo "read-only fs works" && touch /tmp/test 2>&1 || echo "写入 /tmp 失败(预期行为)"'read-only fs workstouch: /tmp/test: Read-only file system写入 /tmp 失败(预期行为)--read-only 让容器的根文件系统变为只读,攻击者无法写入恶意文件。如果应用需要写入临时文件,可以用 --tmpfs /tmp 挂载内存文件系统。
附.4 安全基线检查脚本
CONTAINER=sec-demo
echo "=== 容器安全基线检查 ==="echo "1. 镜像: $(docker inspect -f '{{.Config.Image}}' $CONTAINER)"echo "2. 运行用户: $(docker inspect -f '{{.Config.User}}' $CONTAINER || echo '(默认 root)')"echo "3. 特权模式: $(docker inspect -f '{{.HostConfig.Privileged}}' $CONTAINER)"echo "4. 只读根 FS: $(docker inspect -f '{{.HostConfig.ReadonlyRootfs}}' $CONTAINER)"echo "5. CapAdd: $(docker inspect -f '{{.HostConfig.CapAdd}}' $CONTAINER)"echo "6. CapDrop: $(docker inspect -f '{{.HostConfig.CapDrop}}' $CONTAINER)"echo "7. SecurityOpt: $(docker inspect -f '{{.HostConfig.SecurityOpt}}' $CONTAINER)"安全基线建议:
- 运行用户:非 root(
USER指令或--user) - 特权模式:必须为
false - 只读根 FS:建议
true - CapAdd:应为空或仅添加必要 Capabilities
- CapDrop:建议
ALL,然后按需添加
注意:实验结束后清理:
docker rm -f sec-demo
八、本章小结
上一章理解了docker run 的完整流程。
| 安全机制 | 防护范围 | 默认启用 | 配置方式 |
|---|---|---|---|
| Namespace | 视图隔离 | runc spec | |
| Cgroup | 资源限制 | —memory, —cpus | |
| Capabilities | 权限细分 | (部分) | —cap-add, —cap-drop |
| seccomp | 系统调用过滤 | —security-opt seccomp= | |
| AppArmor | 文件访问控制 | —security-opt apparmor= | |
| User Namespace | 用户映射 | rootless 模式 | |
| 只读文件系统 | 文件不可变 | —read-only |
容器安全不是单一机制能解决的,而是多层防护的组合。最安全的容器配置是:rootless + —cap-drop=ALL + 自定义 seccomp + AppArmor + 只读文件系统 + 资源限制。但这种配置可能影响应用兼容性,需要根据实际场景权衡。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






