mysql事件_MySQL审计插件实现与优化

摘要

审计是数据库安全中很重要的一个环节,它能够实时记录数据库的操作记录,帮助数据库管理员对数据库异常行为进行分析审核。目前除了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层的审计接口,添加了额外的审计埋点对我们所关心的地方进行事件捕获,并将捕获的事件传给审计插件进行处理。

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

4871dd87148e8eccac1b5268f48e94be.png

图2-1 审计流程概况

Server审计入口

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

74396f3b1739427113e748b41852995f.png

图2-2 Sever审计入口

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

39dee2a6b98ff5c23346ecfd2845eb91.png

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

17c25d84a2ae0141625acf1581a6cf16.png

586451877c79c1c83db9113294efe081.png

审计插件声明

审计插件的声明注册了审计事件进入插件的入口函数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

878af105e0083000bbe57be9e0f4ce98.png

图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打开系统日志。

29cdb1cc5db320f2116732178f3a89a7.png

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

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

ccbae8af38d61fe40b31ba42915bf288.png

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

审计日志记录流程

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

660fa53eb22dd3305e938b3d37909d3d.png

图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()将审计日志记录到系统日志。

5039d9937abb1190710b2982a42c38f1.png

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

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

161330ec83daee6c5ecf770a9fe5bb10.png

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

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

724791956eaf7ea69c18b6759a79d1cb.png

图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写入等待。

daa495d25adeee00921be92f61c433fd.png

图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新增变量

32084ae3a50bd0c99016e9632e168ff2.png

性能优化

在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性能优化对比

906adc50dc4c0ab4532b11ca675118f5.png

584d6d1babc6a251111bcf78ab311375.png

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

总结

  1. 从代码分析、功能和性能测试结果来看,目前audit_log审计插件可以满足基本数据库审计需求。
  2. 从审计插件目前已支持的事件类型和Server层的审计入口来看,可以根据业务需求丰富审计日志的记录种类。
  3. 业务开启审计功能之后,审计日志文件的大小会快速增长,需要额外空间存放日志文件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值