Ubuntu服务器上把业务服务和数据库跑在同一台机器,最大的问题不是性能损耗,而是安全边界模糊。一旦Web应用被攻破,攻击者往往能顺着应用权限直接摸到数据库文件、读取配置、甚至提权控制整台服务器。要解决这个问题,核心思路不是装更多防护软件,而是在系统层面把业务与数据库的运行身份、文件访问、网络暴露和进程资源彻底隔开。下面直接讲具体做法。

用独立系统账户切割权限

很多Ubuntu服务器部署时图省事,Web应用和MySQL/PostgreSQL都跑在root或者同一个高权限账户下。正确的做法是给业务服务和数据库各建一个无登录权限的系统账户,并让它们互相无法读取对方的目录。以Nginx/PHP-FPM与MySQL为例,先创建账户:

sudo useradd -r -s /usr/sbin/nologin appuser
sudo useradd -r -s /usr/sbin/nologin dbuser

然后修改PHP-FPM进程池配置,把运行用户从www-data改为appuser。数据库方面,MySQL默认以mysql用户运行,但如果你用的是PostgreSQL,需要确认postgres账户存在且没有shell登录权限。关键点是应用目录的所有者设为appuser,数据库数据目录所有者设为dbuser,权限分别收紧到750甚至700。这样即使Web应用被拿下,攻击者拿到的只是appuser权限,无法直接读取数据库数据文件。

利用AppArmor限制进程行为

Ubuntu默认启用AppArmor,但很多人没有为自定义部署的应用写策略。光靠账户隔离还不够,因为appuser如果被利用,仍然可能通过本地socket或配置文件去连接数据库。AppArmor可以限制进程能访问哪些文件路径、能执行哪些操作。以MySQL为例,Ubuntu自带一个AppArmor配置文件在/etc/apparmor.d/usr.sbin.mysqld,你可以进一步收紧,只允许mysqld访问/var/lib/mysql和必要的日志、socket路径。对PHP-FPM或Node.js业务进程,可以新建一个策略文件:

#include 

/usr/sbin/php-fpm8.1 {
  #include 
  /var/www/app/ r,
  /var/www/app//storage/ rw,
  /run/php/php8.1-fpm.sock rw,
  /var/log/php8.1-fpm.log w,
  deny /var/lib/mysql/ r,
  deny /etc/mysql/ r,
}

加载策略后,即使业务进程被注入恶意代码,它也无法读取数据库目录或配置文件。AppArmor的deny规则是硬隔离,比单纯靠文件权限更可靠,因为文件权限可能因为配置失误被放宽。

数据库只监听本地回环或内网专用接口

如果业务和数据库在同一台Ubuntu服务器上,数据库根本不需要监听公网IP。检查MySQL或PostgreSQL的配置文件,把bind-address设为127.0.0.1,或者直接注释掉外网监听。MySQL 8.0默认只监听本地,但有些第三方脚本会改成0.0.0.0方便远程管理,这等于给攻击者开了一扇门。PostgreSQL的postgresql.conf里listen_addresses要设为localhost。同时检查防火墙规则,Ubuntu的ufw默认可以只开放22、80、443端口,数据库端口3306或5432完全不用对外开放。

如果业务代码和数据库必须通过TCP连接,不要用localhost走网络栈,而是优先用Unix socket。PHP的PDO连接MySQL时,把host写成localhost会走socket,写成127.0.0.1才会走TCP。socket文件本身有权限控制,比TCP端口更难被外部扫描和爆破。

使用容器或systemd沙箱进一步隔离

如果不想用Docker,Ubuntu的systemd本身提供了很强的沙箱能力。你可以为业务服务和数据库分别写service文件,加入NoNewPrivileges、PrivateTmp、ProtectSystem、ProtectHome等指令。例如数据库服务:

[Service]
User=dbuser
Group=dbuser
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/mysql /var/log/mysql
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET

业务服务同样可以加上ProtectSystem=strict,只允许写自己的日志和缓存目录。这样两个进程即使在同一台机器上,文件系统视图和内核接口访问都被限制在最小范围。RestrictAddressFamilies还能限制业务进程只能使用Unix socket和IPv4,数据库进程只能使用Unix socket,从系统调用层面切断不必要的网络能力。

分离配置与密钥管理

业务连接数据库需要账号密码,但不要把密码硬编码在代码里,也不要放在Web目录下。Ubuntu上可以用环境变量文件配合systemd的EnvironmentFile指令,把数据库凭据放在/etc/appdb.conf,权限设为600,所有者设为root,业务进程以appuser运行时通过systemd注入环境变量。这样即使Web目录被完全读取,攻击者也拿不到数据库密码。更进一步,数据库账号本身要按最小权限创建,业务账号只授予对应库的增删改查权限,不要给SUPER、FILE、PROCESS等权限。MySQL里可以这样创建:

CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;

如果业务需要执行DDL变更,单独用另一个管理账号,并且只在维护时使用。

资源隔离防止互相拖垮

安全隔离不仅是权限问题,还包括资源争抢。如果业务服务突然占满CPU或内存,数据库可能跟着崩溃,反过来数据库慢查询也会拖垮业务。Ubuntu上可以用systemd的CPUQuota和MemoryMax限制业务服务的资源使用。例如给PHP-FPM设置:

CPUQuota=200%
MemoryMax=2G

给数据库设置更高的优先级和独立的内存限制,确保数据库不会因为业务进程的异常而被OOM Killer杀掉。同时把数据库的数据目录放在独立的磁盘分区或挂载点上,避免业务日志写满根分区导致数据库无法写入。

日志与审计分离

业务日志和数据库日志要分开存放,并且互相不可见。业务日志目录/var/log/app只允许appuser写,数据库日志/var/log/mysql只允许dbuser写。这样即使业务被入侵,攻击者无法通过读取数据库日志获取查询历史或敏感数据。另外建议开启数据库的审计插件或记录慢查询,但审计日志的存放位置同样要限制权限。Ubuntu的auditd可以监控关键文件访问,比如对/etc/mysql、/var/lib/mysql的读取尝试,一旦业务进程出现异常访问,系统日志里会留下记录。

定期验证隔离效果

配置完成后不要假设隔离已经生效。用sudo -u appuser cat /var/lib/mysql/ibdata1测试是否真的无法读取,用sudo -u dbuser ls /var/www/app测试数据库用户是否看不到业务代码。再检查AppArmor是否处于enforce模式:

sudo aa-status

确认业务和数据库的进程都在各自的策略下运行。如果发现某个策略处于complain模式,要立即调整为enforce。另外定期检查数据库端口是否意外暴露,用ss -tlnp查看监听地址,确保3306或5432只出现在127.0.0.1上。

把这些措施组合起来,业务服务和数据库服务在账户、文件系统、进程能力、网络监听、资源配置五个层面都实现了隔离。即使业务代码被完全控制,攻击者面对的也是一个权限极低、无法读取数据库文件、无法访问数据库端口、资源受限的沙箱环境,数据库本身则受到独立的账户和AppArmor保护。这种隔离不是靠单一工具实现的,而是Ubuntu系统原生能力的组合,维护成本低,安全收益却非常实在。