在Debian系统上使用AppArmor限制Nginx,核心逻辑不是“禁止Nginx运行”,而是“只允许Nginx做它该做的事”。默认安装的Nginx通常运行在一个相对宽松的AppArmor配置下,甚至可能处于“complain”模式,这意味着违规行为只记录不拦截。真正的加固,是把Nginx的配置文件切换到“enforce”模式,并精确定义它能访问哪些文件、能执行哪些程序、能使用哪些网络能力。这能有效防止通过Nginx漏洞(如路径穿越、远程代码执行)对系统造成进一步渗透。

第一步,确认当前AppArmor状态。执行aa-status命令,你会看到已加载的策略列表。如果/usr/sbin/nginx出现在“enforce mode”下,说明已有强制策略;如果出现在“complain mode”下,说明策略存在但不拦截;如果根本没出现,说明Nginx未被AppArmor管理。Debian 12默认安装apparmor和apparmor-utils包,如果没有,用apt install apparmor apparmor-utils安装。

第二步,生成或获取Nginx的AppArmor基础策略。Debian的apparmor-profiles软件包包含了一些预置策略,但Nginx的策略通常需要自行创建或从社区获取。一个稳妥的做法是使用aa-genprof工具:先停止Nginx,执行aa-genprof nginx,然后在另一个终端启动Nginx并模拟正常访问,包括静态文件请求、PHP转发、反向代理等所有业务场景。操作完毕后回到aa-genprof终端按S扫描日志,它会分析Nginx的实际行为并生成初步规则。这个过程叫“学习模式”,生成的规则会保存在/etc/apparmor.d/usr.sbin.nginx文件中。

第三步,理解并手动优化生成的策略文件。自动生成的规则往往过于宽泛,需要人工收紧。打开/etc/apparmor.d/usr.sbin.nginx,你会看到类似这样的结构:

#include <tunables/global>

/usr/sbin/nginx {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/openssl>

  capability net_bind_service,
  capability setgid,
  capability setuid,
  capability dac_override,

  /etc/nginx/ r,
  /etc/ssl/ r,
  /var/log/nginx/* w,
  /var/www/ r,
  /run/nginx.pid rw,
  /run/nginx/* rw,
}

这里逐段解释。第一行#include <tunables/global>是AppArmor的全局可调参数,必须保留。大括号内是具体规则。include abstractions/base允许基本的系统调用如读取系统时间、获取进程信息等,这是Nginx正常运行的基础。abstractions/nameservice允许DNS解析,如果Nginx需要解析upstream域名,这个必须保留;如果只用IP地址做反向代理,可以移除。abstractions/openssl允许读取OpenSSL配置文件,提供HTTPS服务时必须保留。

capability部分是Linux能力机制,比root权限细粒度得多。net_bind_service允许绑定1024以下的特权端口(如80和443),如果Nginx只监听高端口,可以去掉。setgid和setuid允许Nginx的master进程切换为普通用户身份运行worker进程,这是安全标配,必须保留。dac_override允许绕过文件权限检查,通常是因为日志目录或PID文件的属主与Nginx worker用户不一致导致的,更安全的做法是修正文件系统权限,然后移除这条capability。

文件访问规则是防护重点。/etc/nginx/ r表示递归读取nginx配置目录下所有文件,权限为只读。如果你的SSL证书存放在/etc/letsencrypt/目录,需要额外添加/etc/letsencrypt/ r。日志目录/var/log/nginx/* w只给写权限,不需要读权限,除非你配置了日志轮转脚本需要读取。网站根目录/var/www/ r同样只读,如果网站有上传目录,比如/var/www/uploads,需要单独加一条/var/www/uploads/ rw,精确到具体目录,不要对整个网站根目录开放写权限。PID文件和运行时文件需要读写权限,这是Nginx管理进程所必需的。

第四步,处理Nginx的常见扩展场景。如果你的Nginx用作反向代理,需要添加网络权限:

network inet stream,
network inet6 stream,

这允许Nginx发起TCP连接到后端服务器。如果后端在本地Unix socket上,比如PHP-FPM的/run/php/php8.2-fpm.sock,需要添加:

/run/php/php8.2-fpm.sock rw,

如果Nginx需要执行外部程序,比如通过fastcgi_pass或proxy_pass调用,通常不需要执行权限。但如果你使用了第三方模块需要执行外部命令,必须用/usr/bin/某个程序 rix这样的规则精确指定,ix表示继承当前profile执行,避免权限提升。强烈不建议给Nginx任何执行shell或通用解释器的权限。

第五步,处理AppArmor与systemd的兼容性问题。Debian上Nginx通过systemd管理,AppArmor需要在服务启动前加载策略。如果遇到Nginx启动失败,日志中显示“AppArmor denied”信息,用aa-logprof工具分析审计日志并更新策略。执行aa-logprof,它会扫描/var/log/syslog或/var/log/audit/audit.log中的拒绝记录,逐条询问你是否允许。这是一个交互式工具,按A允许,按D拒绝,按I查看详情。每次修改配置文件后,用apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx重新加载策略,然后重启Nginx验证。

第六步,设置策略为强制模式。如果策略文件是从complain模式生成的,用aa-enforce /usr/sbin/nginx切换到强制模式。用aa-status确认Nginx出现在enforce列表中。此时任何违反策略的行为都会被内核拦截并记录。监控日志非常重要,建议配置logwatch或手动定期检查/var/log/syslog中的apparmor记录,关键字为“apparmor="DENIED"”。

一个容易被忽视的细节是Nginx的worker进程和master进程的权限差异。master进程以root启动,需要capability来绑定端口和切换用户;worker进程以www-data运行,权限更小。AppArmor策略默认应用到整个进程树,但可以通过子profile为worker进程设置更严格的限制。不过对于大多数场景,统一策略已经足够,过度细分会增加维护复杂度。

另一个关键点是日志轮转。Nginx的日志轮转通常由logrotate通过postrotate脚本发送USR1信号实现。这个脚本可能由root执行,需要确保AppArmor允许向Nginx进程发信号。在策略中添加:

signal send set=("term", "usr1", "hup") peer=/usr/sbin/nginx,

或者更简单地在logrotate配置中使用invoke-rc.d nginx rotate,这通过init系统发送信号,通常不受AppArmor限制。

如果你的Nginx编译了动态模块,模块文件通常位于/usr/share/nginx/modules/或/usr/lib/nginx/modules/,需要添加对应的读取和执行权限。执行权限用mrix,表示允许映射和执行,这是加载共享库的标准权限。例如:

/usr/lib/nginx/modules/*.so mrix,

对于使用Lua模块的场景,Lua脚本可能存放在/etc/nginx/lua/或类似目录,需要添加读取权限。如果Lua脚本需要访问外部文件或网络,权限需求会更复杂,建议为Lua脚本路径单独设置规则,避免与静态文件规则混淆。

性能影响方面,AppArmor的路径匹配是基于字符串比较的,规则数量在几十条以内时,对Nginx的吞吐量影响可以忽略不计。但如果你有数千条规则或使用了大量通配符,建议用aa-status检查策略加载时间,并用基准测试工具如wrk对比开启和关闭AppArmor时的QPS差异。实际运维中,一个精心编写的Nginx AppArmor策略通常不超过100行规则。

故障排查时,如果Nginx无法启动且systemctl status nginx显示“Permission denied”但没有任何AppArmor日志,可能是策略中没有包含某个必需的抽象层或capability。临时将策略切换到complain模式(aa-complain /usr/sbin/nginx),重启Nginx,然后用aa-logprof收集遗漏的权限。问题解决后再切回enforce模式。注意,生产环境切换模式会导致短暂的防护空窗,建议在维护窗口操作。

最后,AppArmor策略需要纳入配置管理和变更审核流程。策略文件/etc/apparmor.d/usr.sbin.nginx应该和Nginx配置文件一样进行版本控制。每次Nginx升级或配置变更后,都要重新审查AppArmor策略是否需要调整。一个常见的陷阱是Nginx升级后新增了功能或改变了文件路径,导致原有策略拒绝新行为,而运维人员未及时更新策略,造成服务中断。

将AppArmor与Nginx结合,本质上是实践最小权限原则。不要因为怕麻烦而使用过于宽松的规则,也不要一次性收紧所有权限导致业务中断。逐步迭代,从complain模式开始,观察一周的生产流量,收集所有合法行为,然后切换到enforce模式并持续监控。这种渐进式加固方法,既能有效提升安全性,又能避免对业务造成意外影响。