绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
MySQL审计插件实现与优化
2019-12-25 15:18:16

摘要

审计是数据库安全中很重要的一个环节,它能够实时记录数据库的操作记录,帮助数据库管理员对数据库异常行为进行分析审核。目前除了MySQL商业版的审计插件外,还有三种开源的审计插件可以考虑:Percona的审计插件audit log;MariaDB的审计插件server audit;Mcafee的审计插件libaudit_plugin。目前InnoSQL 5.7版本已经加入audit_log审计功能,本文主要对audit_log审计插件的框架和审计日志记录流程进行代码分析。

背景

审计是数据库安全中很重要的一个环节,它能够实时记录数据库的操作记录,帮助数据库管理员对数据库异常行为进行分析审核。使用审计功能的优点:对每一时刻每一用户的操作都有记录。比如某一个业务在某个时间点出现了异常,因为异常操作(比如DDL)导致系统出现了严重的问题,这个时候如果要查看这个问题的具体情况,谁登陆了系统,什么时候登陆的,做了什么操作等等,就可以通过审计log进行调查。当然审计的缺点也很明显,审计日志信息比较大,记录审计日志对Sever的性能有一定的影响。

目前MySQL社区版是没有审计功能的,从5.5开始只在商业版中有,需要单独收取licence费用。除了MySQL商业版的审计插件外,还有三种开源的审计插件可以考虑:

Percona的审计插件audit log:

此插件是Percona 的内置审计插件,兼容性不高,需要适配。

MariaDB的审计插件server audit:

此插件是MariaDB的内置审计插件,MariaDB_5.5.37版本和MariaDB_10.0.10以后版本的audit插件支持MariaDB、 MySQL和Percona Server使用。

Mcafee的审计插件libaudit_plugin:

此插件已经在github上开源,它是McAfee公司基于percona开发的MySQL审计插件。McAfee的MySQL Audit插件以JSON格式保存,日志信息比较大。

目前已经将开源版本Percona-5.7.26-29的audit_log插件适配到InnoSQL-5.7.20版本,基本功能已经测试通过,并对审计性能进行了优化。下面主要对audit_log审计插件的框架和审计日志记录流程进行代码分析。

审计代码分析

从MySQL5.5开始,内核中已经增加了一套server层的审计接口,添加了额外的审计埋点对我们所关心的地方进行事件捕获,并将捕获的事件传给审计插件进行处理。

审计插件对支持的事件按照设定的过滤规则和记录格式进行处理后,根据设定的日志记录方式,将审计日志记录到文件或系统日志中。

图2-1 审计流程概况

Server审计入口

下面是mysql服务器层审计机制中调用审计入口函数mysql_audit_notify()的地方,但audit_log插件并不支持所有的事件。其中只有红色部分的事件被audit_log支持,包括connect/disconnect连接断开,query语句,change_user命令,以及触发器、存储过程和事件中SQL语句。另外audit_log插件会在安装和卸载插件时,记录Audit/NoAudit审计日志。

图2-2 Sever审计入口

在plugin_audit.h中,server层提供了11种审计事件,

但是从审计插件声明和进入插件入口函数中的判断来看,插件注册了三种事件,但根据设置的审计策略不同,也只会处理GENERAL和CONNECT两种事件。

审计插件声明

审计插件的声明注册了审计事件进入插件的入口函数audit_log_notify,审计插件支持的事件类型,以及审计插件初始化和释放函数,审计插件状态变量和系统变量。其中每种事件类型有不同的子事件类型,只有红色事件对应的审计日志才会被处理记录,这里也对应着2.1中的审计入口说明。

MYSQL_AUDIT_GENERAL_ALL

MYSQL_AUDIT_GENERAL_LOG

MYSQL_AUDIT_GENERAL_ERROR

MYSQL_AUDIT_GENERAL_RESULT

MYSQL_AUDIT_GENERAL_STATUS

MYSQL_AUDIT_CONNECTION_ALL

MYSQL_AUDIT_CONNECTION_CONNECT

MYSQL_AUDIT_CONNECTION_DISCONNECT

MYSQL_AUDIT_CONNECTION_CHANGE_USER

MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE

MYSQL_AUDIT_TABLE_ACCESS_ALL

MYSQL_AUDIT_TABLE_ACCESS_READ

MYSQL_AUDIT_TABLE_ACCESS_INSERT

MYSQL_AUDIT_TABLE_ACCESS_UPDATE

MYSQL_AUDIT_TABLE_ACCESS_DELETE

图2-3-1 审计插件声明

安装审计初始化函数主要是初始化系统变量、锁以及对系统变量合理性进行检查,并将过滤变量用户名,命令,数据库名称的值(逗号分隔)解析出来并插入哈希表。然后根据系统变量audit_log_handler设置的日志存储方式(FILE/SYSLOG)调用init_new_log_file()进行相应的初始化,如果是FILE格式并且使用buffer缓存(审计策略audit_log_strategy为ASYNCHRONOUS或者PERFORMANCE),则创建刷新线程audit_log_flush_worker。然后打开审计日志文件。如果是SYSLOG格式,则调用openlog打开系统日志。

图2-3-2 审计插件安装初始化函数

卸载审计插件会调用deinit函数,先记录卸载日志,然后再关闭日志文件并释放空间。

图2-3-3 审计插件卸载函数

审计日志记录流程

Sever层在需要进行审计的地方调用审计入口函数mysql_audit_notify(),首先检查插件是否支持该事件类型,如果不支持则return不进入审计日志的记录流程。如果支持该事件,则从thd中获取query和字符集信息,然后将事件发给审计插件,调用audit_log_notify()进入审计日志记录流程。

图2-4-1 Server审计入口

审计入口函数audit_log_notify(),根据传入事件的信息判断是否满足用户、库名、SQL命令的过滤条件,再判断审计策略audit_log_policy是否满足ALL、NONE、LOGINS或者QUERIES中的一种,如果是MYSQL_AUDIT_GENERAL_CLASS事件类型并且是MYSQL_AUDIT_GENERAL_STATUS子事件,则调用audit_log_general_record()按照审计日志的格式获取日志信息,并调用audit_log_write()记录审计日志。如果是MYSQL_AUDIT_CONNECTION_CLASS事件,并且是子事件MYSQL_AUDIT_CONNECTION_CONNECT、MYSQL_AUDIT_CONNECTION_DISCONNECT或者MYSQL_AUDIT_CONNECTION_CHANGE_USER中的一种,则调用audit_log_connection_record()按照审计日志的格式获取日志信息,并调用audit_log_write()记录审计日志。

Audit_log_write()调用handler->write()写入日志,如果日志是记录到审计日志文件中,则调用audit_handler_file_write(),判断如果使用buffer即audit_log_strategy= ASYNCHRONOUS或 PERFORMANCE,则调用audit_handler_file_write_buf()将日志写入buffer缓冲;如果不使用buffer,即audit_log_strategy= SYNCHRONOUS或SEMISYNCHRONOUS,则调用audit_handler_file_write_nobuf()将日志直接写入文件缓存,接着判断如果audit_log_strategy= SYNCHRONOUS,则调用logger_sync()将文件缓存同步刷入磁盘。如果是记录到系统日志SYSLOG中,则调用audit_handler_syslog_write()->syslog()将审计日志记录到系统日志。

图2-4-2 审计插件内部入口流程

对于使用buffer方式记录审计日志的情况,会根据日志大小进行不同的方式写入。如果日志长度大于buffer大小,并且日志记录策略不是PERFORMANCE(缓冲区满时删消息),则会调用audit_handler_file_write_buf()将日志写入文件缓冲区。如果日志写入位置加上写入日志长度小于等于文件刷新位置加上buffer大小,则将日志信息拷贝至日志buffer,否则如果日志记录策略不是PERFORMANCE,则等待日志刷新线程完成日志刷新后继续loop判断将日志写入buffer。如果日志写入的大小超过buffer大小的一半时,则立即唤醒刷新线程将buffer中的日志写入文件缓冲区。

图2-4-3 使用buffer的审计日志写入

如果不使用buffer,则调用audit_handler_file_write_nobuf()将日志内容直接写入文件缓冲区,再判断如果刷新线程刷完buffer日志,并且设置audit_log_rotations大于0,即使用日志文件循环记录,并且日志文件大小没有达到ulonglong-1,但日志文件大小超过了日志文件循环的阈值audit_log_rotate_on_size,则调用do_rotate()进行日志文件循环。

图2-4-3 不使用buffer的审计日志写入

日志刷新线程

前面提到,如果是将审计日志记录文件中,并且使用buffer的方式记录,那么会创建一个刷新线程将buffer中的日志写入文件缓冲区。这里介绍一下刷新线程的工作流程。

audit_log_flush_worker()循环判断,如果日志文件没有关闭;或者文件关闭,但是buffer中仍有未刷入文件的日志,即log->flush_pos 不等于 log->write_pos,则调用audit_log_flush()进行刷新。

刷新函数audit_log_flush()中循环判断如果buffer中没有新写入的审计日志,即log->flush_pos等于 log->write_pos,并且日志文件没有关闭,则等待1s循环或者等待被唤醒,即当buffer日志写入大小超过一半时会唤醒刷新等待。如果日志文件关闭,则return返回。

如果buffer中有新的审计日志写入,并且判断如果log->flush_pos 大于等于 log->write_pos % log->size,即log->write_pos大于等于buffer size,则将log->size - log->flush_pos大小的日志写入日志文件缓冲,此时buffer中的日志并未写完,还有log->write_pos-log->size大小等待写入,状态标志为LOG_RECORD_INCOMPLETE,并将log->flush_pos置0,log->write_pos对buffer size取模,再将写入文件缓冲区的日志大小累计至audit_log_flushlen。如果log->flush_pos 小于 log->write_pos % log->size,则将log-> write_pos - log->flush_pos大小的日志写入文件缓冲,buffer日志写完,状态标志为LOG_RECORD_INCOMPLETE,并将写入文件缓冲区的日志大小累计至audit_log_flushlen。

如果audit_log_fsync_size不为0,并且文件缓冲区的日志量大于等于设置的阈值audit_log_fsync_size,则调用fsync将日志文件缓冲区的日志刷入磁盘,刷盘后将audit_log_flushlen置0,并广播刷新完成信号,唤醒buffer写入等待。

图2-5 刷新线程处理流程

审计日志格式说明

默认OLD格式,日志格式可以通过设置audit_log_format为OLD(XML格式)、NEW(NEW要比OLD的标签详细一些)、JSON和CSV。

审计日志提供了三种事件监控记录:audit、query 和 connection。每种事件都有不同的字段记录审计日志信息(红色字段为InnoSQL新增字段):

AUDIT事件字段说明:

NAME:事件名称,Audit表示log开始,NoAudit表示log结束

RECORD:的记录ID

TIMESTAMP:时间戳

MYSQL_VERSION:server版本号

STARTUP_OPTIONS:命令行启动参数

OS_VERSION:操作系统版本号

QUERY事件字段说明:

NAME:事件名称,如Query, Prepare, Execute, Change user等

RECORD:的记录ID

TIMESTAMP:时间戳

COMMAND_CLASS: 操作命令类,如select, alter_table, create_table等

CONNECTION_ID:连接ID

STATUS:非0表示有error

SQLTEXT:SQL语句

DIGEST_TEXT:记录SQL模板

QUERY_TIME:记录SQL执行时间

LOCK_TIME:记录SQL加锁时间

SCAN_ROWS:记录SQL扫描行数

AFFECTED_ROWS:记录SQL更新行数

USER:连接用户名

HOST:连接主机名

OS_USER:外部用户名

IP:连接用户IP

DB:连接时指定的数据库名

CONNECTION事件字段说明:

NAME:事件名称,如Connect, Quit

RECORD:的记录ID

TIMESTAMP:时间戳

CONNECTION_ID:连接ID

STATUS:0表示连接成功,非0表示连接失败

USER:连接用户名

PRIV_USER:经过身份验证的用户名

OS_LOGIN:外部用户名

PROXY_USER:代理用户名

HOST:连接主机名

IP:连接用户IP

DB:连接时指定的数据库名

审计系统变量说明

注:红色为InnoSQL新增变量

性能优化

在Percona开源audit_log版本中存在性能周期性波动的问题,在InnoSQL的audit_log版本中进行了优化:

Percona性能问题:刷新线程只将内存buffer中的审计日志写入文件缓冲区,并未主动调用fsync()将文件缓冲刷入磁盘,而是让操作系统内核自己fsync,操作系统调用pdflush线程定期/proc/sys/vm/dirty_expire_centisecs(单位是1/100秒,默认3000,即30s)刷盘,这会导致sysbench在8核16GB配置的云主机测试20G数据32并发oltp_read_write时会出现TPS 30s左右周期性剧烈波动。

优化方案:增加文件缓冲大小的阈值系统变量audit_log_fsync_size,在异步刷新线程audit_log_flush_worker()->audit_log_flush()中累计已经写入文件缓冲中的日志大小audit_log_flushlen,如果阈值设置不为0,并且audit_log_flushlen超过文件缓冲阈值大小audit_log_fsync_size,则进行fsync,同时累计文件缓冲大小audit_log_flushlen置0。

下面是优化前后与未开启审计的性能对比:

表5-1 TPS性能优化对比

图5-1 性能优化曲线对比图

总结

从代码分析、功能和性能测试结果来看,目前audit_log审计插件可以满足基本数据库审计需求。

从审计插件目前已支持的事件类型和Server层的审计入口来看,可以根据业务需求丰富审计日志的记录种类。

业务开启审计功能之后,审计日志文件的大小会快速增长,需要额外空间存放日志文件。

分享好友

分享这个小栈给你的朋友们,一起进步吧。

数据库内核开发
创建时间:2019-12-11 16:43:06
网易数据库内核技术专家 8年多数据库和存储系统开发经验,《MySQL内核:InnoDB存储引擎 卷1》作者之一,申请技术专利10+,已授权5+。曾主导了网易公有云RDS、MongoDB等数据库云服务建设 现负责网易MySQL分支InnoSQL开发和维护。专注于数据库内核技术和分布式系统架构,擅长分析解决疑难问题。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 温正湖
    栈主

小栈成员

查看更多
  • xzh1980
  • else
  • Jack2k
  • at_1
戳我,来吐槽~