4.4.8编写审计插件

本节描述如何编写一个服务器端审计插件,使用在插件/ audit_nullMySQL源代码发行版目录。的audit_null.c该目录中的源文件实现了一个简单的示例审计插件名为NULL_AUDIT

在服务器中,可插入的审计接口在sql_audit.h而且sql_audit.cc文件sqlMySQL源代码发行版目录。此外,当可审计事件发生时,服务器中的几个地方会调用审计接口,以便在必要时通知已注册的审计插件有关该事件。要查看此类调用发生在何处,请在服务器源文件中搜索具有表单名称的函数调用mysql_audit_xxx().审计通知发生在服务器操作中,例如:

  • 客户端连接和断开事件

  • 向通用查询日志写入消息(如果该日志已启用)

  • 向错误日志写入消息

  • 向客户端发送查询结果

要编写审计插件,请在插件源文件中包含以下头文件。根据插件的功能和需求,可能还需要其他MySQL或通用头文件。

# include < mysql / plugin_audit.h >

plugin_audit.h包括plugin.h,因此不需要显式包含后一个文件。plugin.h定义了MYSQL_AUDIT_PLUGIN服务器插件类型和声明插件所需的数据结构。plugin_audit.h定义审计插件特定的数据结构。

审计插件,像任何MySQL服务器插件一样,有一个通用的插件描述符(参见第4.4.2.1节“服务器插件库和插件描述符”)和特定于类型的插件描述符。在audit_null.c的一般描述符audit_null是这样的:

mysql_declare_plugin(audit_null) {MYSQL_AUDIT_PLUGIN, /*类型*/ &audit_null_descriptor, /* descriptor */ "NULL_AUDIT", /* name */ "Oracle Corp", /* author */ "Simple NULL Audit", /* description */ PLUGIN_LICENSE_GPL, audit_null_plugin_init, /* init函数(加载时)*/ audit_null_plugin_deinit, /* deinit函数(卸载时)*/ 0x0003, /*版本*/ simple_status, /*状态变量*/ NULL, /*系统变量*/ NULL, 0,} mysql_declare_plugin_end;

第一个成员,MYSQL_AUDIT_PLUGIN,将此插件标识为审计插件。

audit_null_descriptor指向特定于类型的插件描述符,稍后将进行描述。

的名字成员(NULL_AUDIT)表示在语句中引用插件时使用的名称,例如安装插件卸载插件.这也是显示的名称INFORMATION_SCHEMA。插件显示插件

一般的插件描述符也指simple_status对象暴露几个状态变量的结构显示状态声明:

静态结构st_mysql_show_var simple_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},{“Audit_null_general_status”(char *) &number_of_calls_general_status SHOW_INT},{“Audit_null_connection_connect”,(char *) &number_of_calls_connection_connect, SHOW_INT}, {"Audit_null_connection_disconnect", (char *) &number_of_calls_connection_disconnect, SHOW_INT}, {"Audit_null_connection_change_user", (char *) &number_of_calls_connection_change_user, SHOW_INT}, {0,0,0}};

audit_null_plugin_init初始化函数在加载插件时将状态变量设置为零。的audit_null_plugin_deinit函数在卸载插件时执行清理。在操作过程中,插件会对它接收到的每个通知增加第一个状态变量。它还根据事件类和子类增加其他类。实际上,第一个变量是事件子类计数的总和。

audit_null_descriptor的值指向特定于类型的插件描述符。对于审计插件,该描述符具有以下结构:

Struct st_mysql_audit {int interface_version;空白(* release_thd) (MYSQL_THD);void (*event_notify)(MYSQL_THD, unsigned int, const void *);无符号长class_mask [MYSQL_AUDIT_CLASS_MASK_SIZE];};

审计插件的特定类型插件描述符有以下成员:

  • interface_version:按照惯例,特定于类型的插件描述符以给定插件类型的接口版本开头。服务器检查interface_version当它加载插件时,看插件是否与它兼容。对于审计插件,的值interface_version成员是MYSQL_AUDIT_INTERFACE_VERSION(定义在plugin_audit.h).

  • release_thd:一个函数,服务器调用它来通知插件它正在从线程上下文中分离出来。这应该是如果没有这样的函数。

  • event_notify:一个函数,服务器调用它来通知插件发生了可审计的事件。这个函数不应该是;这没有意义,因为不会发生审计。

  • class_mask:一个位掩码,指示插件想要接收通知的事件类。如果该值为0,则服务器不向插件传递事件。

服务器使用event_notify而且release_thd函数在一起。它们在特定线程的上下文中调用,线程可能执行产生多个事件通知的活动。服务器第一次调用event_notify对于线程,它创建插件到线程的绑定。当此绑定存在时,无法卸载插件。当线程不再发生事件时,服务器通过调用release_thd函数,然后销毁绑定。例如,当客户端发出一条语句时,处理该语句的线程可能会通知审计插件该语句产生的结果集以及记录该语句的情况。在这些通知发生后,服务器释放插件,然后让线程进入睡眠状态,直到客户端发出另一条语句。

的第一次调用中,该设计使插件能够为给定线程分配所需的资源event_notify功能和释放他们在release_thd功能:

Event_notify函数:如果线程需要内存,则分配内存…剩下的通知处理…函数:如果分配了内存,就释放内存…剩下的发布处理…

这比在通知函数中重复分配和释放内存更有效。

NULL_AUDIT审计插件,特定于类型的插件描述符如下所示:

静态结构st_mysql_audit audit_null_descriptor= {MYSQL_AUDIT_INTERFACE_VERSION, /*接口版本*/ NULL, /* release_thd函数*/ audit_null_notify, /*通知函数*/ {(unsigned long) MYSQL_AUDIT_GENERAL_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK} /*类掩码*/};

服务器调用audit_null_notify ()将审计事件信息传递给插件。插件没有release_thd函数。

事件类掩码指示对所有事件的兴趣一般而且连接类。plugin_audit.h为这些类及其对应的类掩码定义符号:

#定义MYSQL_AUDIT_CONNECTION_CLASS (1 << MYSQL_AUDIT_CONNECTION_CLASS) #定义MYSQL_AUDIT_CONNECTION_CLASS (1 << MYSQL_AUDIT_CONNECTION_CLASS)

类的第二个和第三个参数event_notify函数原型表示事件类和指向事件结构的泛型指针:

void (*event_notify)(MYSQL_THD, unsigned int, const void *);

不同类中的事件可能具有不同的结构,因此通知函数应该使用事件类值来确定如何解释指向事件结构的指针。

如果服务器使用事件类调用通知函数MYSQL_AUDIT_GENERAL_CLASS,它将事件结构作为指向a的指针传递mysql_event_general结构:

结构mysql_event_general {unsigned int event_subclass;int general_error_code;无符号长general_thread_id;const char * general_user;unsigned int general_user_length;const char * general_command;unsigned int general_command_length;const char * general_query;unsigned int general_query_length;struct charset_info_st * general_charset; unsigned long long general_time; unsigned long long general_rows; };

审计插件可以解释mysql_event_general成员如下:

  • event_subclass:事件子类,以下值之一:

    #定义MYSQL_AUDIT_GENERAL_LOG 0 #定义MYSQL_AUDIT_GENERAL_ERROR 1 #定义MYSQL_AUDIT_GENERAL_RESULT 2 #定义MYSQL_AUDIT_GENERAL_STATUS
  • general_error_code:错误码。返回的值类似于mysql_errno ()C API函数;0表示没有错误。

  • general_thread_id:事件发生的线程ID。

  • general_user:事件的当前用户。

  • general_user_length的长度。general_user在字节。

  • general_command:对于一般查询日志事件,表示操作类型。例子:连接查询关闭.对于错误日志事件,错误消息。返回的值类似于mysql_error ()C API函数;空字符串意味着没有错误。对于结果事件,这是空的。

  • general_command_length的长度。general_command在字节。

  • general_query:被记录或产生结果的SQL语句。

  • general_query_length的长度。general_query在字节。

  • general_charset:事件的字符集信息。

  • general_time:一个时间戳值,指示在调用通知函数之前的时间。

  • general_rows:对于一般查询日志事件,为零。对于错误日志事件,表示发生错误的行号。对于结果事件,为结果中的行数加1。对于不产生结果集的语句,该值为0。这种编码允许将不产生结果集的语句与产生空结果集的语句区分开来。例如,对于a删除语句中,此值为0。对于一个选择,结果总是1或更多,其中1表示空结果集。

  • general_host:对于一般查询日志事件,表示客户端主机名的字符串。

  • general_sql_command:对于一般查询日志事件,表示所执行操作类型的字符串,例如连接drop_table

  • general_external_user:对于一般查询日志事件,表示外部用户的字符串(如果没有则为空)。

  • general_ip:对于一般查询日志事件,客户端IP地址字符串。

general_hostgeneral_sql_commandgeneral_external_user,general_ip成员是MySQL 5.6.14中的新成员。这些都是MYSQL_LEX_STRING结构将字符串与其长度配对。例如,如果event_general是指向一般事件的指针,可以访问成员general_host值如下:

event_general - > general_host。长度event_general - > general_host.str

如果服务器使用事件类调用通知函数MYSQL_AUDIT_CONNECTION_CLASS,它将事件结构作为指向a的指针传递mysql_event_connection结构,它类似于和解释的方式大致相同mysql_event_general结构。

NULL_AUDIT插件通知函数非常简单。它增加一个全局事件计数器,确定事件类,然后查看事件子类来确定要增加哪个子类计数器:

静态void audit_null_notify(MYSQL_THD thd __attribute__((未使用)),unsigned int event_class, const void *event){/*容易出现race,哦好吧*/ number_of_calls++;if (event_class == MYSQL_AUDIT_GENERAL_CLASS) {const struct mysql_event_general *event_general= (const struct mysql_event_general *)事件;switch (event_general->event_subclass) {case MYSQL_AUDIT_GENERAL_LOG: number_of_calls_general_log++;打破;案例MYSQL_AUDIT_GENERAL_ERROR: number_of_calls_general_error + +;打破;案例MYSQL_AUDIT_GENERAL_RESULT: number_of_calls_general_result + +;打破;案例MYSQL_AUDIT_GENERAL_STATUS: number_of_calls_general_status + +;打破; default: break; } } else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS) { const struct mysql_event_connection *event_connection= (const struct mysql_event_connection *) event; switch (event_connection->event_subclass) { case MYSQL_AUDIT_CONNECTION_CONNECT: number_of_calls_connection_connect++; break; case MYSQL_AUDIT_CONNECTION_DISCONNECT: number_of_calls_connection_disconnect++; break; case MYSQL_AUDIT_CONNECTION_CHANGE_USER: number_of_calls_connection_change_user++; break; default: break; } } }

要编译和安装插件库文件,请使用第4.4.3节“编译和安装插件库”.要使库文件可用,请将其安装到插件目录(由plugin_dir系统变量)。为NULL_AUDIT当你从源代码构建MySQL时,它会被编译和安装。它也包含在二进制发行版中。构建过程生成一个名称为的共享对象库adt_null.so(所以后缀可能因平台而异)。

要在运行时注册插件,使用此语句,调整所以为您的平台添加必要的后缀:

安装插件adt_null.so

有关插件加载的其他信息,请参见安装和卸载插件

要验证插件安装,请检查INFORMATION_SCHEMA。插件表或使用显示插件声明。看到获取服务器插件信息

在安装审计插件时,它会暴露状态变量,指示调用插件的事件:

mysql> SHOW STATUS LIKE 'Audit_null%';+-----------------------------------+-------+ | Variable_name |值  | +-----------------------------------+-------+ | Audit_null_called | 1388 | | Audit_null_connection_change_user | 0 | | Audit_null_connection_connect 22 | | | Audit_null_connection_disconnect 21 | | | Audit_null_general_error | 1 | | Audit_null_general_log | 513 | | Audit_null_general_result | 415 | | Audit_null_general_status | 416年  | +-----------------------------------+-------+

Audit_null_called计数所有事件,其他变量计数事件子类的实例。例如,前面的显示状态语句导致服务器向客户机发送一个结果,并在启用通用查询日志时向该日志写入一条消息。因此,反复发出该语句的客户机会导致Audit_null_called而且Audit_null_general_result每次递增,并且Audit_null_general_log递增。在MySQL 5.6.24之前,只有启用了通用查询日志,才会收到通用查询日志的事件通知。从5.6.24开始,无论日志是否启用,都会收到通知。)

要在测试后禁用插件,使用以下语句卸载它:

卸载插件NULL_AUDIT;