MySQL:如何编写Audit Plugin审计插件

转载请署名:印风


在之前我已经写了一系列介绍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事件,当完成

COM_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

用户名长度

priv_user

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官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值