apparmor为web服务配沙箱的关键是精准放行必要路径与能力,而非盲目deny;需严格匹配二进制路径命名profile、显式声明读写权限、deny高危路径与危险capability,并验证进程绑定enforce模式。

直接给 Web 服务配 AppArmor 沙箱,关键不是“全封死”,而是“只放行它真正需要的路径和能力”。盲目 deny 一切会导致服务启动失败或功能异常,比如 Nginx 找不到 SSL 证书、PHP-FPM 无法写 session 文件——这些都不是策略太严,而是没搞清它的运行时依赖。
确认 Web 服务可执行路径与默认 profile 名称
AppArmor 策略文件名必须严格匹配进程的 exec 路径(不是服务名),且路径中 / 要替换成 .。例如:
- Nginx 主进程通常为
/usr/sbin/nginx→ 对应 profile 文件名是usr.sbin.nginx - Apache httpd 默认是
/usr/sbin/apache2→ profile 名为usr.sbin.apache2 - 若用 systemd 启动但实际二进制在
/opt/myweb/bin/server→ profile 必须叫opt.myweb.bin.server
运行 ps auxf | grep nginx 或 systemctl status nginx 查看真实 ExecStart= 路径;别依赖 service 名或包名。改名或加 wrapper 脚本后,profile 就失效。
最小化文件路径权限:读写分离 + 显式 deny
Web 服务绝大多数路径只需只读,但少数目录(如日志、缓存、上传临时区)需写入。AppArmor 不支持“默认拒绝”,必须显式声明所有允许项,再对高危路径做 deny。
Linux AI 服务器 — 将 Linux 服务器转换为本地 AI 推理集群。使用 systemd、NVIDIA CUDA 实现无头 Linux AI,零 GUI 开销。
- 静态资源目录(
/var/www/html/**)设为r,不加w;若需 CMS 后台自动更新,则只放开子路径如/var/www/html/wp-content/** rw, - 日志目录统一挂
tmpfs到/tmp/myweb-logs/,策略中只写/tmp/myweb-logs/** rw,,避免污染宿主机/var/log - 必须显式阻断:
deny /proc/sys/** w,、deny /sys/fs/cgroup/** w,、deny /etc/shadow r,—— 这些不写,攻击者仍可通过 procfs 探测宿主机信息 - 证书路径(如
/etc/ssl/private/**)只给r,且确保文件权限已是600;AppArmor 不替代文件系统权限
禁用危险 capability 与 mount 权限
Web 服务几乎从不需要 sys_admin、sys_module 或 net_admin。只要保留 net_bind_service(绑定 80/443),其他一律 drop。
- 在 profile 中添加:
capability net_bind_service,(仅此一项) - 明确禁用:
deny capability sys_admin,、deny capability sys_module,、deny capability dac_override, - 禁止挂载:
deny mount,和deny umount,—— 防止通过mount --bind绕过路径限制 - 如果服务用到
chroot或pivot_root(极少见),需额外放行,但更推荐用容器 USER namespace 替代
验证策略是否生效并进入 enforce 模式
加载后不等于生效。很多问题出在 profile 没被正确关联到进程,或仍处于 complain 模式。
- 检查是否加载:
sudo aa-status | grep -A5 "myweb",确认 profile 名出现在 “profiles are loaded” 列表中 - 确认进程已绑定:
sudo aa-status --pid $(pgrep -f "nginx: master"),输出应含 profile 名,而非unconfined - 查当前模式:
sudo cat /proc/$(pgrep -f "nginx: master")/attr/current,返回类似unconfined表示未绑定,usr.sbin.nginx (enforce)才算成功 - 若看到
(complain),说明还在记录模式,需运行sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
最容易被忽略的是:profile 名必须与进程真实 exec 路径完全一致,且加载后必须重启服务进程(不是 reload),否则旧进程仍跑在 unconfined 下。沙箱逃逸往往就发生在 reload 后“以为生效了”的那几分钟里。










