数据库审计实战:MySQL 操作留痕配置

数据库审计是数据安全建设中最容易被跳过的一环。应用层有 WAF 与访问日志,系统层有 auditd,但数据库这一层往往只剩错误日志和慢查询日志,谁在什么时候执行了 SELECT * FROM users、谁批量导出过客户表,事后完全无法追溯。本文给出 MySQL 与 PostgreSQL 的数据库审计配置方案,包含插件安装、日志落盘、轮转与完整性保护,命令可直接复制执行。

适用场景

  • 数据库账号被多个应用或运维人员共用,出问题无法定位到具体操作者;
  • 发生过误删数据、批量导出、越权查表等事件,需要事后追溯;
  • 等保测评或行业合规要求数据库操作留存不少于六个月;
  • 需要建立数据库访问基线,识别异常时间、异常来源 IP 的查询行为。

前置条件

  • MySQL 5.7/8.0 或 PostgreSQL 12 及以上版本,具备 SUPER 或 pgaudit 所需权限;
  • 已评估磁盘容量,审计日志体积通常是业务数据写入量的数十倍;
  • 具备独立的日志分区或日志采集通道,避免与数据目录争抢 IO;
  • PostgreSQL 需要重启实例以加载 shared_preload_libraries。

原理说明

数据库日志分三类,用途完全不同。错误日志记录启动、崩溃与复制异常,不含业务操作;慢查询日志按执行时间阈值采样,只覆盖性能问题;审计日志则记录连接、查询、DDL 与权限变更等全部或指定类别的事件,是唯一能满足合规留痕要求的类型。

实现路径按版本区分。MySQL 社区版不包含官方的 audit_log 插件,该插件仅在企业版提供。社区环境可选三条路:安装 MariaDB Audit Plugin 或 Percona Audit Log Plugin,开启通用查询日志 general_log 做全量采集,或在应用与数据库之间部署数据库代理做旁路审计。PostgreSQL 的官方方案是 pgaudit 扩展,通过 shared_preload_libraries 预加载,再按语句类别分级记录。

需要强调两点。通用查询日志会显著增加 IO 与磁盘占用,不适合长期在生产高并发库上全量开启,通常用于临时排查或写入压力较小的库;审计日志必须做权限收敛与异地转发,否则攻击者拿到数据库权限后第一件事就是清空审计文件,日志反而成为无效证据。

操作步骤

1. MySQL:安装 MariaDB Audit Plugin

-- 确认插件文件存在
SHOW VARIABLES LIKE 'plugin_dir';
-- 通常为 /usr/lib/mysql/plugin/server_audit.so

INSTALL PLUGIN server_audit SONAME 'server_audit.so';
SHOW PLUGINS;

写入配置文件持久化,避免重启失效:

# /etc/my.cnf
[mysqld]
plugin-load-add          = server_audit.so
server_audit_logging      = ON
server_audit_events       = CONNECT,QUERY,TABLE,QUERY_DDL,QUERY_DML
server_audit_output_type  = FILE
server_audit_file_path    = /var/log/mysql/audit.log
server_audit_file_rotate_size = 1073741824
server_audit_file_rotations   = 30
server_audit_excl_users   = monitor_user

关键参数说明:server_audit_events 决定记录范围,生产环境建议至少包含 CONNECT,QUERY_DDL,QUERY_DML;server_audit_excl_users 用于排除监控账号,避免告警类探测刷满日志;server_audit_file_rotations 配合 rotate_size 实现按体积轮转。

2. MySQL:通用查询日志(可选,低负载库)

SET GLOBAL log_output = 'FILE';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';
SET GLOBAL general_log = ON;

-- 确认状态
SHOW VARIABLES LIKE 'general_log%';

配置文件中使用 general_log=1 与 log_output=FILE。切回关闭只需 SET GLOBAL general_log = OFF;,无需重启。

3. PostgreSQL:启用 pgaudit

# 安装扩展包(Debian/Ubuntu)
apt-get install -y postgresql-16-pgaudit

# postgresql.conf
shared_preload_libraries = 'pgaudit'
pgaudit.log              = 'ddl, role, write'
pgaudit.log_catalog      = off
pgaudit.log_relation     = on
pgaudit.log_statement_once = on
logging_collector        = on
log_directory            = 'log'
log_filename             = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age         = 1d
log_rotation_size        = 1GB

重启实例后创建扩展:

sudo systemctl restart postgresql
sudo -u postgres psql -c "CREATE EXTENSION pgaudit;"

pgaudit.log_catalog = off 用于过滤系统表查询产生的噪声,pgaudit.log_relation = on 会为每个涉及的表单独记录一行,便于按表检索。

4. 日志权限收敛与轮转

mkdir -p /var/log/mysql && chown mysql:mysql /var/log/mysql
chmod 750 /var/log/mysql
chmod 640 /var/log/mysql/audit.log

# /etc/logrotate.d/mysql-audit
/var/log/mysql/audit.log {
    daily
    rotate 180
    size 1G
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
    create 640 mysql mysql
}

使用 copytruncate 而非 create 是关键:审计插件持续持有文件句柄,重命名后不重新打开会导致后续写入丢失。若日志需要异地留存,用 rsyslog 或 filebeat 转发到独立日志服务器,转发链路使用 TLS 加密。

5. 审计规则与告警

# auditd/文件监控:审计日志被删除时立即告警
/var/log/mysql/audit.log -w -p wa -k db_audit_tamper

# 典型异常检索
grep -E "DROP TABLE|TRUNCATE|GRANT ALL" /var/log/mysql/audit.log | tail -50
grep -c "QUERY,root" /var/log/mysql/audit.log

配置验证

# 1. 插件是否加载并处于开启状态
mysql -e "SHOW PLUGINS\G" | grep -A2 server_audit
mysql -e "SHOW VARIABLES LIKE 'server_audit%';"

# 2. 产生一条测试操作,确认被记录
mysql -e "SELECT 1 FROM mysql.user LIMIT 1;"
sudo tail -n 5 /var/log/mysql/audit.log

# 3. PostgreSQL 审计是否生效
sudo -u postgres psql -c "CREATE TABLE audit_probe(id int);"
sudo tail -n 20 /var/log/postgresql/postgresql-*.log | grep -i audit

# 4. 权限与轮转检查
ls -l /var/log/mysql/audit.log
logrotate -d /etc/logrotate.d/mysql-audit

第 2 步应看到形如 db,localhost,root,QUERY,mysql,"SELECT 1 FROM mysql.user LIMIT 1" 的记录。若插件已加载但日志为空,优先检查 server_audit_events 是否为空值、以及日志目录属主是否为 mysql。

常见问题

FAQ 1:开启了审计,性能下降多少?

取决于事件范围与写入方式。仅记录 CONNECT 与 QUERY_DDL 时开销通常在个位数百分比以内;记录全量 QUERY 且日志与数据同盘时,高并发写入场景可能下降两成以上。优化方向有三个:把日志目录放到独立 SSD 或通过 SYSLOG 输出绕过本地文件 IO;用 server_audit_excl_users 排除监控与备份账号;对读多写少的库才考虑全量记录。

FAQ 2:审计日志会不会记录到密码等敏感内容?

会。连接类事件的记录中包含用户名与主机,而 SET PASSWORD、GRANT ... IDENTIFIED BY 一类语句会以明文形式落在日志里。因此审计日志目录权限必须收紧到 640 mysql:mysql,禁止普通账号读取;同时应在应用侧推动改用密钥认证或短时凭据,减少明文口令出现在 SQL 语句中的机会。日志转发链路也必须用加密通道。

FAQ 3:社区版 MySQL 没有 audit_log 插件怎么办?

三条可行路径:安装 MariaDB Audit Plugin(多版本兼容、社区广泛使用);改用 Percona Server,其内置 audit_log 插件功能更完整,支持过滤规则与 JSON 输出;或采用数据库代理旁路审计,在代理层解析并落盘,对数据库本身零侵入,但需要额外评估代理的可用性风险。

FAQ 4:审计日志被高权限账号删除了怎么办?

本地日志无法防住拥有系统权限的攻击者,必须做异地转发。用 filebeat、rsyslog 或数据库自身的主从复制把日志实时送到独立日志服务器或对象存储,并在源端配置 auditd 规则监控审计文件的写入与删除操作,触发即告警。合规场景下建议同时启用对象存储的合规保留策略,写入后不可删除、不可修改。

总结

数据库审计的价值在于”事后可追溯”与”事前可威慑”。落地时按三步推进:先按类别开启审计事件并确认性能影响可接受,再完成日志轮转与权限收敛,最后接入异地转发与篡改告警。只开日志不做保护,等于把证据放在攻击者随手可及的位置;只做告警不做留存,则无法满足合规要求。把数据库审计纳入与系统日志同等的运营标准,才算补齐数据安全的最后一环。