技术博客

Docker 运行时安全:Seccomp、AppArmor、SELinux 与 Capabilities 裁剪

深入讲解 Docker 容器运行时的安全加固机制:Seccomp(系统调用过滤)的工作原理与自定义 Profile 配置(屏蔽危险 syscall)、AppArmor Profile 定制(限制容器文件系统和网络访问)、SELinux 与 Docker 的集成(CentOS/RHEL 环境的 SELinux 标签)、Linux Capabilities 的精确裁剪(按应用需求最小化),以及 OPA/Gatekeeper 策略级别的安全基线控制。

Docker安全SeccompAppArmorSELinuxcapabilities运行时安全容器安全

上一篇讲了“拒绝”策略(非 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,避免手动编写产生遗漏。