转载请署名:印风
在之前我已经写了一系列介绍audit plugin的博文,当时还很青涩,这里把之前的内容整理出来,并成为mysql plugin编写手册系列的一部分
-----------------------------------------------------------------------------------------------
1.什么是Audit Plugin
在MySQL5.5之前,MySQL本身缺少一套的对服务器操作的审计机制,对于非法或者危险的操作、错误捕捉、登录审计尚不能很好的支持。如果谁drop了一个表或者不慎不带where的删除表数据,这种危险的操作应该被明确的记录下来。
在MySQL5.5里,添加了额外的流程来对这些我们所关心的地方进行事件捕获;然后将捕获到的事件传递给Audit Plugin;而Audit Plugin所要做的,就是对这些事件进行判别,并做必要的反应,比如记录到log或发送一个报警
2.Audit Plugin能做什么
实际上,每个SQL都会触发审计插件,并且,在审计插件中,我们可以获得执行SQL的THD对象;而THD对象中包含了非常丰富的信息,包括用户名、客户端IP、当前操作的数据库等信息;这为我们进行审计提供了无限的可能;
但要注意的是,如果不是我们关心的审计类型,应尽量避免过多的操作,以免影响系统的性能。
3.如何编写Audit Plugin
新增加的审计插件其实很简单,其实现思想是在内核代码的不同位置增加相应的接口函数,具体实现在sql_audit.cc和sql_audit.h文件,这里我们只涉及到Plugin的编写,有兴趣的同学可以看看审计是如何实现的。
Audit Plugin能够识别不同的实现,具体包括两种类型,连接审计和SQL执行审计,以宏来定义类型。
1)第一种事件类型:General Class
宏定义 | 描述 |
MYSQL_AUDIT_GENERAL_CLASS 0 | 事件的类型为general |
MYSQL_AUDIT_GENERAL_CLASSMASK (1 << MYSQL_AUDIT_GENERAL_CLASS) | 掩码,用于声明插件关注的审计类型 |
MYSQL_AUDIT_GENERAL_LOG 0 | LOG EVENT,在提交到general query log前触发 |
MYSQL_AUDIT_GENERAL_ERROR 1 | ERROR EVENT,在将错误传递给用户前触发 |
MYSQL_AUDIT_GENERAL_RESULT 2 | RESULT EVENT,在将结果集传递给用户后触发 |
MYSQL_AUDIT_GENERAL_STATUS 3 | STATUS EVENT,在将结果集或错误传递给用户后触发 |
2)第二种事件类型:CONNECTIONCLASS
宏定义 | 描述 |
MYSQL_AUDIT_CONNECTION_CLASS 1 | 标记事件类型为CONNECTION |
MYSQL_AUDIT_CONNECTION_CLASSMASK (1 << MYSQL_AUDIT_CONNECTION_CLASS) | 掩码,用于声明插件关注的审计类型 |
MYSQL_AUDIT_CONNECTION_CONNECT 0 | CONNECT事件,客户端连接完成认证阶段 |
MYSQL_AUDIT_CONNECTION_DISCONNECT 1 | DISCONNECT事件,连接被终止时 |
MYSQL_AUDIT_CONNECTION_CHANGE_USER 2 | CHANGE_USER事件,当完成 |
3)声明插件
Audit插件的定义结构体为st_mysql_audit
字段 | 类型 | 描述 |
interface_version | int | 插件版本: MYSQL_AUDIT_INTERFACE_VERSION |
release_thd | void (*release_thd)(MYSQL_THD) | 当线程将被释放时被调用 |
event_notify | void (*event_notify)(MYSQL_THD, unsigned int, const void *); | 当事件发生时调用该函数 |
class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE] | unsigned long | 掩码,用来标示关注的事件类型 |
在官方文档里这么解释release_thd和event_notify:
MYSQL服务器可以将这两个函数联合起来一起用,在每个特定线程内调用,当某个线程第一次调用event_notify时,创建了一个线程与插件的绑定,当这种绑定存在时,插件是不可以卸载的,当没有事件发生在线程上时,Server通过调用release_thd来告诉插件,然后销毁绑定;例如一个客户端在执行一个SQL,完成一系列的消息通知后,在没有任务sleep之前,会通知到插件。
那么这种方式有什么用呢?我们可以在event_notify函数中分配内存,并在release_thd中将内存释放。这显然比每次调用event_notify时分配内存要效率更高
4)event_notify函数
这是处理审计事件的主函数,函数原型为:
void (*event_notify)(MYSQL_THD, unsigned int, const void*)
第1个参数表示当前的线程THD
第2个参数表示事件的类型:
MYSQL_AUDIT_CONNECTION_CLASS 或者MYSQL_AUDIT_GENERAL_CLASS
第三个参数表示当前事件的详细信息,我们可以根据第二个参数转换为相应的结构体类型。
GENERAL类型对应的结构体为mysql_event_general,如下所示:
字段 | 类型 | 描述 |
event_subclass | unsigned int | 子事件类型 |
general_error_code | int | 错误码,可以调用mysql_errno()来获得具体信息 |
general_user | const char* | 当前事件的用户名 |
general_user_length | unsigned int | 用户名长度 |
general_command | const char* | 对于general quere log子事件,表示命令,例如Connect、Query、Shutdown等;对于ERROR类型存储错误信息,其他子事件类型为NULL |
general_query | const char* | 当前线程的SQL语句 |
general_query_length | unsigned int | SQL长度 |
general_charset | struct charset_info_st * | 字符集 |
general_time | unsigned long long | event_notify被调用时的时间戳 |
general_rows | unsigned long | 对于general query log子事件类型为0,对于ERROR子事件表示发生错误的行号,对于RESULT子事件,表示产生的行数,对于那些不会产生结果集的SQL,其值为0,这区别与select,如果select的结果集为空,该值为1 |
CONNECTION类型对应的结构体为mysql_event_connection,如下所示:
字段 | 类型 | 描述 |
event_subclass | unsigned int | 子事件类型 |
thread_id | unsigned long | 线程id |
user | const char* | 用户名 |
user_length | unsigned int | 用户名长度 |
const char* |
| |
priv_user_length | unsigned int |
|
external_user | const char* |
|
external_user_length | unsigned int |
|
proxy_user | const char* | 代理用户名(似乎在auth插件里有提到,后面再议) |
proxy_user_length | unsigned int | 长度 |
host | const char* | 主机名 |
host_length | unsigned int | 主机名长度 |
ip | const char* | 客户端IP |
ip_length | unsigned int | IP长度 |
database | const char* | 客户端连接的数据库名 |
database_length | unsigned int | 数据库名长度 |
4.一个简单的例子:audit_null(摘自MySQL源代码),这个示例用于审计GENERAL类型的事件,并提供了三个状态变量。虽然示例很简单,但给出了最基本的流程。
#include <stdio.h>
#include <mysql/plugin.h>
#include <mysql/plugin_audit.h>
#if !defined(__attribute__) &&(defined(__cplusplus) || !defined(__GNUC__) || __GNUC__ == 2 && __GNUC_MINOR__ < 8)
#define __attribute__(A)
#endif
static volatile int number_of_calls; /* forSHOW STATUS, see below */
static volatile int number_of_calls_general_log;
static volatile intnumber_of_calls_general_error;
static volatile intnumber_of_calls_general_result;
/*
Initialize the plugin at server start or plugin installation.
SYNOPSIS
audit_null_plugin_init()
DESCRIPTION
Does nothing.
RETURN VALUE
0 success
1 failure(cannot happen)
*/
static int audit_null_plugin_init(void *arg__attribute__((unused)))
{
number_of_calls= 0;
number_of_calls_general_log= 0;
number_of_calls_general_error= 0;
number_of_calls_general_result= 0;
return(0);
}
/*
Terminate the plugin at server shutdown or plugin deinstallation.
SYNOPSIS
audit_null_plugin_deinit()
Does nothing.
RETURN VALUE
0 success
1 failure(cannot happen)
*/
static int audit_null_plugin_deinit(void*arg __attribute__((unused)))
{
return(0);
}
/*
Foo
SYNOPSIS
audit_null_notify()
thd connectioncontext
DESCRIPTION
*/
static void audit_null_notify(MYSQL_THD thd__attribute__((unused)),
unsigned intevent_class,
const void*event)
{
/*prone to races, oh well */
number_of_calls++;
if(event_class == MYSQL_AUDIT_GENERAL_CLASS)
{
const struct mysql_event_general *event_general=
(const struct mysql_event_general *) event;
switch (event_general->event_subclass)
{
case MYSQL_AUDIT_GENERAL_LOG:
number_of_calls_general_log++;
break;
case MYSQL_AUDIT_GENERAL_ERROR:
number_of_calls_general_error++;
break;
case MYSQL_AUDIT_GENERAL_RESULT:
number_of_calls_general_result++;
break;
default:
break;
}
}
}
/*
Plugin type-specific descriptor
*/
static struct st_mysql_auditaudit_null_descriptor=
{
MYSQL_AUDIT_INTERFACE_VERSION, /* interface version */
NULL, /*release_thd function */
audit_null_notify, /* notifyfunction */
{(unsigned long) MYSQL_AUDIT_GENERAL_CLASSMASK } /* class mask */
};
/*
Plugin status variables for SHOW STATUS
*/
static struct st_mysql_show_varsimple_status[]=
{
{"Audit_null_called", (char *) &number_of_calls, SHOW_INT },
{"Audit_null_general_log", (char *) &number_of_calls_general_log,SHOW_INT },
{"Audit_null_general_error", (char *)&number_of_calls_general_error,
SHOW_INT },
{"Audit_null_general_result", (char *)&number_of_calls_general_result,
SHOW_INT },
{0, 0, 0}
};
/*
Plugin library descriptor
*/
mysql_declare_plugin(audit_null)
{
MYSQL_AUDIT_PLUGIN, /*type */
&audit_null_descriptor, /*descriptor */
"NULL_AUDIT", /* name */
"Oracle Corp", /* author */
"Simple NULL Audit", /* description */
PLUGIN_LICENSE_GPL,
audit_null_plugin_init, /*init function (when loaded) */
audit_null_plugin_deinit, /*deinit function (when unloaded) */
0x0002, /*version */
simple_status, /*status variables */
NULL, /*system variables */
NULL
}
mysql_declare_plugin_end;
后记:
MySQL5.5对审计的支持使得我们可以更好的扩展插件功能,由于我们可以在多个地方进行审计,实际上相当于在服务器上给出了很多个断点,我们可以据此发挥想象力……
1. 针对特定的SQL记录到log,或发送警报给数据库OWNER
2. 记录每个数据库甚至每个表的SQL执行量,结合I_S插件显示出来
3. ……
MYSQL的插件的自由度非常高,但需要牢记的是,插件是工作在MYSQL的内存中的,需要仔细编写插件并测试,否则可能造成严重的服务器Crash事件。
参考:
MySQL5.5.16源代码
MySQL5.5官方文档