Docker 运行时安全:Seccomp、AppArmor、SELinux 与 Capabilities 裁剪
深入讲解 Docker 容器运行时的安全加固机制:Seccomp(系统调用过滤)的工作原理与自定义 Profile 配置(屏蔽危险 syscall)、AppArmor Profile 定制(限制容器文件系统和网络访问)、SELinux 与 Docker 的集成(CentOS/RHEL 环境的 SELinux 标签)、Linux Capabilities 的精确裁剪(按应用需求最小化),以及 OPA/Gatekeeper 策略级别的安全基线控制。
上一篇讲了“拒绝”策略(非 root、只读文件系统)。本文讲“强制限制”策略:即使容器内进程是 root,也通过内核层的 Seccomp、AppArmor、SELinux 限制它能做什么。这是容器安全加固的第二道防线。
运行时安全的三层防护
Docker 运行时安全体系(由内到外):
层1 - Capabilities(Linux 内核)
控制:进程能做哪些特权操作
粒度:~40 个 capability 开关
层2 - Seccomp(系统调用过滤)
控制:进程能调用哪些系统调用
粒度:300+ 个 Linux syscall 逐一过滤
实现:BPF 程序在内核中过滤
层3 - AppArmor / SELinux(MAC 强制访问控制)
控制:进程能访问哪些文件/网络
粒度:路径级别、端口级别
实现:LSM(Linux Security Module)
一、Seccomp(系统调用过滤)
1.1 Seccomp 工作原理
应用进程发起系统调用
↓
Seccomp BPF 程序检查(内核态)
↓
允许(ALLOW)→ 系统调用正常执行
拒绝(KILL)→ 进程被 SIGKILL
拒绝(ERRNO)→ 返回错误码(更友好)
拒绝(TRACE)→ 通知 ptrace 监控进程
1.2 Docker 默认 Seccomp Profile
# Docker 默认已开启 Seccomp(屏蔽约 40 个危险 syscall)
docker info | grep -i seccomp
# Security Options:
# seccomp
# Profile: builtin ← 使用内置 profile
# 查看 Docker 内置 Seccomp profile(哪些被允许/拒绝)
# https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
# 被屏蔽的危险 syscall 示例:
# kexec_load → 加载新内核
# mount → 挂载文件系统(生产容器一般不需要)
# ptrace → 调试其他进程(调试类工具需要)
# swapon → 操作 swap 分区
# syslog → 操作内核日志缓冲区
# open_by_handle_at → 文件句柄操作(容器逃逸路径)
# 关闭 Seccomp(仅用于调试,生产不要这样做)
docker run --security-opt seccomp=unconfined myapp
1.3 自定义 Seccomp Profile
// seccomp-web-app.json
// 精简版:Web 应用通常只需要以下 syscall
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
"syscalls": [
{
"names": [
// 进程管理
"clone", "fork", "vfork", "execve", "exit", "exit_group",
"wait4", "waitid", "kill", "getpid", "getppid", "gettid",
// 内存管理
"mmap", "mprotect", "munmap", "brk", "madvise",
"mlock", "munlock", "mlockall", "mincore",
// 文件 IO
"read", "write", "pread64", "pwrite64", "readv", "writev",
"open", "openat", "close", "stat", "fstat", "lstat",
"lseek", "access", "faccessat", "dup", "dup2", "dup3",
"mkdir", "mkdirat", "rmdir", "unlink", "unlinkat",
"rename", "renameat", "truncate", "ftruncate",
"chmod", "fchmod", "fchmodat", "chown", "fchown", "lchown",
"getdents", "getdents64", "readlink", "readlinkat",
"symlink", "symlinkat", "link", "linkat",
"futex", "set_robust_list", "get_robust_list",
// 网络(Web 应用必需)
"socket", "connect", "accept", "accept4", "bind",
"listen", "getsockname", "getpeername", "getsockopt", "setsockopt",
"sendto", "recvfrom", "sendmsg", "recvmsg",
"shutdown", "socketpair", "select", "pselect6",
"poll", "ppoll", "epoll_create", "epoll_create1",
"epoll_ctl", "epoll_wait", "epoll_pwait",
// 信号
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "rt_sigsuspend",
"sigaltstack", "kill", "tgkill",
// 时间
"gettimeofday", "clock_gettime", "clock_getres",
"nanosleep", "clock_nanosleep",
// 系统信息
"uname", "sysinfo", "getrlimit", "setrlimit", "prlimit64",
"getrusage", "times", "getcpu",
// 用户/组
"getuid", "getgid", "geteuid", "getegid",
"getgroups", "setgroups", "setuid", "setgid",
"setresuid", "setresgid", "getresuid", "getresgid",
// 进程组/会话
"getpgrp", "setpgrp", "getsid", "setsid",
// 杂项
"prctl", "arch_prctl", "ioctl", "fcntl",
"pipe", "pipe2", "timerfd_create", "eventfd", "eventfd2",
"signalfd", "signalfd4", "inotify_init", "inotify_init1",
"inotify_add_watch", "inotify_rm_watch",
"getrandom", "seccomp", "set_tid_address",
"restart_syscall"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
# 使用自定义 Seccomp profile
docker run -d \
--security-opt seccomp=./seccomp-web-app.json \
myapp
# 或在 compose.yml 中
# services:
# api:
# security_opt:
# - seccomp:./seccomp-web-app.json
1.4 生成最小 Seccomp Profile(使用 oci-seccomp-bpf-hook)
# 工具:自动记录容器运行时使用的 syscall,生成精确 profile
# 安装
dnf install oci-seccomp-bpf-hook # RHEL/Fedora
# 录制模式:运行容器并记录所有 syscall
docker run \
--annotation io.containers.trace-syscall=of:./app-seccomp.json \
--security-opt seccomp=unconfined \
myapp sh -c "执行典型工作负载的操作"
# 生成的 profile 只包含实际用到的 syscall
# 基于 DENY 模式会更精确(只允许录制到的 syscall)
二、AppArmor
2.1 AppArmor 基础
# 检查 AppArmor 状态(Ubuntu/Debian 系统)
sudo apparmor_status
# apparmor module is loaded.
# 45 profiles are loaded.
# 45 profiles are in enforce mode.
# 0 profiles are in complain mode.
# 2 processes have profiles defined.
# 2 processes are in enforce mode.
# Docker 默认 AppArmor profile
# 文件位置:/etc/apparmor.d/docker-default
# 应用于所有未指定 profile 的容器
# 查看容器使用的 AppArmor profile
docker inspect myapp --format '{{.AppArmorProfile}}'
# docker-default
2.2 自定义 AppArmor Profile
# /etc/apparmor.d/docker-web-app
#include <tunables/global>
profile docker-web-app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# 文件系统规则
# 只读访问应用目录
/app/** r,
/app/static/** r,
# 允许写入的目录(日志、临时文件)
/app/logs/** rw,
/tmp/** rw,
# 系统库(只读)
/usr/lib/** r,
/lib/** r,
/lib64/** r,
# 进程运行需要的路径
/proc/sys/kernel/ngroups_max r,
/proc/@{pid}/attr/** rw,
/proc/@{pid}/fd/ r,
# 网络规则
network inet tcp, # 允许 TCP IPv4
network inet udp, # 允许 UDP(DNS)
network inet6 tcp, # 允许 TCP IPv6
# 禁止的操作(默认拒绝,这里只是显式说明)
deny /etc/passwd w, # 不能修改 passwd
deny /etc/shadow rw, # 不能读写 shadow
deny @{PROC}/sys/kernel/shmmax w, # 不能修改内核参数
# 允许的 capability(与 --cap-add 联动)
capability net_bind_service,
capability chown,
# 信号
signal (send, receive) peer=docker-web-app,
}
# 加载 AppArmor Profile
sudo apparmor_parser -r /etc/apparmor.d/docker-web-app
sudo apparmor_status | grep docker-web-app
# 应用到容器(enforce 模式)
docker run -d \
--security-opt apparmor=docker-web-app \
myapp
# 测试模式(complain 模式,只记录不阻断)
docker run -d \
--security-opt apparmor=docker-web-app-complain \
myapp
# 查看违规日志
sudo tail -f /var/log/audit/audit.log | grep apparmor
# 或
sudo dmesg | grep apparmor
三、SELinux(CentOS/RHEL)
# 检查 SELinux 状态
sestatus
# SELinux status: enabled
# SELinuxfs mount: /sys/fs/selinux
# SELinux mount point: /sys/fs/selinux
# Loaded policy name: targeted
# Current mode: enforcing ← 强制模式
# Docker 与 SELinux 集成
# Docker daemon 自动使用 SELinux 标签(如果系统启用了 SELinux)
docker info | grep -i selinux
# Security Options:
# seccomp
# selinux ← SELinux 已集成
# 容器文件的 SELinux 标签
docker exec myapp ls -Z /app/
# -rwxr-xr-x. app app system_u:object_r:container_file_t:s0:c123,c456 server
# 主机挂载 volume 时需要添加 :z 或 :Z 标签
docker run -d \
-v /data/myapp:/app/data:z \ # z:共享标签(多个容器共用)
myapp
docker run -d \
-v /data/myapp:/app/data:Z \ # Z:私有标签(只有这个容器能访问)
myapp
# 不加 :z/:Z 会导致:
# Permission denied(即使文件权限是 777,SELinux 也会拒绝)
# SELinux 临时宽松模式(调试时)
setenforce 0 # 切换到 permissive(只记录不阻断)
setenforce 1 # 切回 enforcing
# 永久配置(/etc/selinux/config)
# SELINUX=enforcing ← 生产推荐
# SELINUX=permissive ← 调试
# SELINUX=disabled ← 关闭(不推荐)
# 查看 SELinux 拒绝日志
grep avc /var/log/audit/audit.log | tail -20
# 自动生成 SELinux 策略模块(audit2allow 工具)
grep avc /var/log/audit/audit.log | audit2allow -M my-docker-app
semodule -i my-docker-app.pp
四、综合安全配置
# compose.yml — 生产安全加固完整示例
services:
api:
image: harbor.company.com/backend/api:v1.2.3
user: "1000:1000"
read_only: true
# Capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# 安全选项
security_opt:
- no-new-privileges:true
- seccomp:./seccomp-web-app.json # 自定义 Seccomp profile
# Ubuntu/Debian 环境加 AppArmor:
# - apparmor:docker-web-app
# 临时文件系统(替代写入操作)
tmpfs:
- /tmp:size=100m,mode=1777,noexec
- /var/run:size=10m
# 资源限制(避免 DoS)
deploy:
resources:
limits:
cpus: '2'
memory: 512M
# 只读 volume(只挂载需要的)
volumes:
- api-logs:/app/logs # 只有日志目录可写
# 健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
# 网络(最小化)
networks:
- backend
networks:
backend:
internal: true # 禁止此网络访问外网(内部服务)
安全配置优先级建议
优先级 配置项 影响范围 改造成本
─────────────────────────────────────────────────────────────
P0(必做)
✓ 以非 root 用户运行(USER 指令) 应用层 低
✓ --security-opt no-new-privileges 运行时 零
✓ 去掉 --privileged 运行时 零(排查是否需要)
✓ 不挂载 docker.sock 运行时 低
P1(推荐)
✓ --cap-drop ALL + 按需 cap-add 运行时 低
✓ --read-only + tmpfs 运行时 中
✓ 自定义 Seccomp Profile 运行时 中
P2(高安全场景)
✓ AppArmor / SELinux Profile 系统层 高
✓ User Namespace Remapping Daemon 层 中(需重启 daemon)
✓ Rootless Docker Daemon 层 高
小结
Seccomp、AppArmor、SELinux 代表了“纵深防御”的核心理念:即使攻击者突破了应用层,内核层的强制访问控制也能限制损害范围。
实际落地建议:先实施 P0 级别的改造(成本最低,收益最大),然后在高安全场景再引入 Seccomp 和 AppArmor。SELinux 在 RHEL/CentOS 系统上默认已经开启,确保不要关闭它,volume 挂载时记得加 :z 或 :Z 标签即可。
Seccomp 的一个实用技巧:先用 --security-opt seccomp=unconfined 配合 syscall 追踪工具录制容器实际用到的调用,再生成最小 Profile,避免手动编写产生遗漏。
