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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章