15.10.4 容器安全基线(权限、capabilities、seccomp)

容器安全基线用于降低容器逃逸与横向移动风险,核心围绕“最小权限、最小能力、最小系统调用面”展开。通过限定用户、能力集与系统调用,配合只读文件系统与安全配置,可在不显著影响业务的前提下提升整体安全性。

原理草图(权限/能力/系统调用)

文章图片

一、权限最小化(用户/文件系统/挂载)
- 避免以 root 运行:使用 -u 指定非特权用户或在镜像中创建业务用户。
- 文件系统最小化:将根文件系统设为只读 --read-only,并仅对必须的目录挂载可写卷。
- 访问控制:限制宿主机路径挂载,避免 -v /:/host 等高风险挂载;避免 --privileged
- 网络隔离:按业务划分网络,禁用不必要的端口与服务。

示例:创建非 root 用户并只读运行

# /opt/app/Dockerfile
FROM alpine:3.19
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY app.sh /app/app.sh
RUN chmod +x /app/app.sh
USER app
CMD ["/app/app.sh"]
# 构建镜像
docker build -t app:secure /opt/app

# 只读运行,/tmp 与 /var/lib/app 为可写
docker run --rm -d \
  --name app1 \
  --read-only \
  -v appdata:/var/lib/app \
  -v /tmp \
  -u 1000:1000 \
  app:secure

# 预期效果:容器内无法写入根目录
docker exec -it app1 sh -c "touch /etc/testfile || echo 'write denied'"

二、Capabilities 管控
Linux capabilities 将 root 权限拆分为细粒度能力,容器应只保留必要能力:
- 默认 drop 全部:--cap-drop=ALL
- 按需添加:--cap-add=NET_BIND_SERVICE(监听低端口)
- 高风险能力:SYS_ADMINNET_ADMINSYS_MODULESYS_PTRACE 等默认移除

示例:仅允许低端口监听

# 解释:
# --cap-drop=ALL  删除所有默认能力
# --cap-add=NET_BIND_SERVICE  允许绑定 1024 以下端口
docker run --rm -d \
  --name web80 \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  -p 80:80 \
  nginx:1.25

三、Seccomp 策略(系统调用最小化)
- 默认策略:Docker 默认 seccomp profile 已禁止高风险系统调用,可作为基础。
- 自定义策略:根据应用需要裁剪系统调用,重点限制 ptracemountkexecclone 等高风险调用。

示例:自定义 seccomp(禁止 ptrace)

// /opt/seccomp/deny-ptrace.json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "archMap": [
    {"architecture": "SCMP_ARCH_X86_64", "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]}
  ],
  "syscalls": [
    {
      "names": ["ptrace"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}
# 使用自定义 seccomp
docker run --rm -d \
  --name app2 \
  --security-opt seccomp=/opt/seccomp/deny-ptrace.json \
  app:secure

# 验证:尝试执行 ptrace(示例容器内若有相关命令会失败)
docker exec -it app2 sh -c "ptrace 2>/dev/null || echo 'ptrace blocked'"

四、推荐基线组合
- 运行用户:非 root 用户,镜像内设置 USER app
- 权限:--read-only,仅挂载必要可写卷
- 能力:--cap-drop=ALL --cap-add=NET_BIND_SERVICE(按需补充)
- 安全选项:--security-opt no-new-privileges --security-opt seccomp=profile.json
- 资源与系统:禁用特权模式,不使用 --privileged

示例:完整运行基线

docker run --rm -d \
  --name app-sec \
  --read-only \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --security-opt seccomp=/opt/seccomp/deny-ptrace.json \
  -v appdata:/var/lib/app \
  -v /tmp \
  -p 8080:8080 \
  app:secure

五、排错与审计
1. 容器写入失败(只读 FS)

# 排查写入路径
docker exec -it app-sec sh -c "touch /etc/a || echo 'readonly fs'"
# 解决:将必需写目录挂载为可写
docker run ... -v appdata:/var/lib/app
  1. 端口绑定失败(capabilities 不足)
# 报错示例:bind: permission denied
# 解决:添加 NET_BIND_SERVICE
docker run ... --cap-add=NET_BIND_SERVICE
  1. seccomp 拒绝系统调用导致应用异常
# 通过 dmesg 或容器日志观察系统调用拒绝
dmesg | grep -i seccomp
# 解决:按需放行特定 syscall 或回退到默认 profile
  1. 审计与基线检查
# 查看容器是否特权
docker inspect app-sec --format '{{.HostConfig.Privileged}}'

# 查看 capabilities
docker inspect app-sec --format '{{.HostConfig.CapDrop}} {{.HostConfig.CapAdd}}'

# 查看是否只读
docker inspect app-sec --format '{{.HostConfig.ReadonlyRootfs}}'

六、练习
1. 以非 root 用户运行一个 Nginx 容器,并确保能监听 80 端口。
2. 使用 --read-only 运行应用,找出必须写入的目录并以卷挂载。
3. 制作一个 seccomp 策略,仅允许应用所需的系统调用,并验证对高风险调用的阻断效果。
4. 编写脚本对运行中的容器进行基线审计(检查 privileged、capabilities、readonly)。