扩展 MySQL 8.0  / 第 4 章 MySQL 插件 API  / 4.4 编写插件  /  4.4.8 编写审计插件

4.4.8 编写审计插件

本节介绍如何使用 plugin/audit_nullMySQL 源代码分发目录中的示例插件编写服务器端审计插件。该audit_null.c目录中的源文件实现了一个名为NULL_AUDIT.

在服务器内部,可插入审计接口在MySQL 源代码分发目录中sql_audit.hsql_audit.cc文件中 实现。sql此外,当可审计事件发生时,服务器中的几个地方会调用审计接口,以便在必要时通知已注册的审计插件有关该事件的信息。要查看此类调用发生的位置,请搜索服务器源文件以查找名称为 . 服务器操作会出现审核通知,例如: mysql_audit_xxx()

  • 客户端连接和断开连接事件

  • 将消息写入一般查询日志(如果启用了日志)

  • 将消息写入错误日志

  • 向客户端发送查询结果

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

#include <mysql/plugin_audit.h>

plugin_audit.hincludes 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,         /* 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) */
  0x0003,                     /* version                         */
  simple_status,              /* status variables                */
  NULL,                       /* system variables                */
  NULL,
  0,
}
mysql_declare_plugin_end;

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

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

name成员 ( ) 表示在or NULL_AUDIT等​​语句中用于引用插件的 名称。这也是 或 显示的名称。 INSTALL PLUGINUNINSTALL PLUGININFORMATION_SCHEMA.PLUGINSSHOW PLUGINS

通用插件描述符也指 simple_status,一个将几个状态变量暴露给SHOW STATUS语句的结构:

static struct 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;
  void (*release_thd)(MYSQL_THD);
  void (*event_notify)(MYSQL_THD, unsigned int, const void *);
  unsigned long class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
};

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

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

  • release_thd:服务器调用的一个函数,用于通知插件它正在与其线程上下文分离。NULL如果没有这样的功能, 应该 是这样。

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

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

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

这种设计使插件能够在第一次调用函数时为给定线程分配所需的资源,并在 event_notify函数中释放它们release_thd

event_notify function:
  if memory is needed to service the thread
    allocate memory
  ... rest of notification processing ...

release_thd function:
  if memory was allocated
    release memory
  ... rest of release processing ...

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

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

static struct st_mysql_audit audit_null_descriptor=
{
  MYSQL_AUDIT_INTERFACE_VERSION,                    /* interface version    */
  NULL,                                             /* release_thd function */
  audit_null_notify,                                /* notify function      */
  { (unsigned long) MYSQL_AUDIT_GENERAL_CLASSMASK |
                    MYSQL_AUDIT_CONNECTION_CLASSMASK } /* class mask        */
};

服务器调用audit_null_notify()以将审计事件信息传递给插件。该插件没有任何 release_thd功能。

事件类掩码表示对通用连接 类的所有事件感兴趣。plugin_audit.h为这些类定义符号及其相应的类掩码:

#define MYSQL_AUDIT_GENERAL_CLASS 0
#define MYSQL_AUDIT_GENERAL_CLASSMASK (1 << MYSQL_AUDIT_GENERAL_CLASS)

#define MYSQL_AUDIT_CONNECTION_CLASS 1
#define MYSQL_AUDIT_CONNECTION_CLASSMASK (1 << MYSQL_AUDIT_CONNECTION_CLASS)

在特定于类型的插件描述符中,event_notify函数原型的第二个和第三个参数表示事件类和指向事件结构的通用指针:

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

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

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

struct mysql_event_general
{
  unsigned int event_subclass;
  int general_error_code;
  unsigned long 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: 事件子类,以下值之一:

    #define MYSQL_AUDIT_GENERAL_LOG 0
    #define MYSQL_AUDIT_GENERAL_ERROR 1
    #define MYSQL_AUDIT_GENERAL_RESULT 2
    #define MYSQL_AUDIT_GENERAL_STATUS 3
  • general_error_code: 错误代码。这是一个类似于 mysql_errno()C API 函数返回的值;0 表示没有错误。

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

  • general_user:事件的当前用户。

  • general_user_length: 的长度 general_user,以字节为单位。

  • general_command:用于一般查询日志事件,操作类型。例子: Connect, Query, Shutdown。对于错误日志事件,错误消息。这是一个类似于 mysql_error()C API 函数返回的值;空字符串表示没有错误。 对于结果事件,这是空的。

  • general_command_length: 的长度 general_command,以字节为单位。

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

  • general_query_length: 的长度 general_query,以字节为单位。

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

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

  • general_rows:对于一般查询日志事件,为零。对于错误日志事件,发生错误的行号。对于结果事件,结果中的行数加一。对于不产生结果集的语句,该值为 0。此编码使不产生结果集的语句与产生空结果集的语句区分开来。例如,对于DELETE语句,此值为 0。对于 a SELECT,结果始终为 1 或更多,其中 1 表示空结果集。

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

  • general_sql_command:对于一般查询日志事件,一个字符串,指示执行的操作类型,例如connectdrop_table

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

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

general_host、 和 成员是 MySQL 5.6.14 中的新成员general_sql_command。 这些是将字符串及其长度配对的结构。例如,如果 是指向一般事件的指针,您可以 按如下方式访问值的成员: general_external_usergeneral_ipMYSQL_LEX_STRINGevent_generalgeneral_host

event_general->general_host.length
event_general->general_host.str

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

NULL_AUDIT插件通知功能非常简单 。它递增全局事件计数器,确定事件类,然后查看事件子类以确定要递增哪个子类计数器:

static void audit_null_notify(MYSQL_THD thd __attribute__((unused)),
                              unsigned int event_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;
    case MYSQL_AUDIT_GENERAL_STATUS:
      number_of_calls_general_status++;
      break;
    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.so 后缀可能因平台而异)的共享对象库。

要在运行时注册插件,请使用此语句,.so根据需要调整平台的后缀:

INSTALL PLUGIN NULL_AUDIT SONAME 'adt_null.so';

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

要验证插件安装,请检查 INFORMATION_SCHEMA.PLUGINS表或使用SHOW PLUGINS 语句。请参阅获取服务器插件信息

安装审计插件时,它会公开状态变量,指示已调用插件的事件:

mysql> SHOW STATUS LIKE 'Audit_null%';
+-----------------------------------+-------+
| Variable_name                     | Value |
+-----------------------------------+-------+
| 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计算所有事件,其他变量计算事件子类的实例。例如,前面的SHOW STATUS语句使服务器向客户端发送结果,并在启用该日志的情况下将消息写入常规查询日志。因此,重复发出该语句的客户端导致 每次递增,并且Audit_null_called递增 。(在 MySQL 5.6.24 之前,只有启用了通用查询日志,才会收到通用查询日志的事件通知。从 5.6.24 开始,无论是否启用该日志,都会收到通知。) Audit_null_general_resultAudit_null_general_log

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

UNINSTALL PLUGIN NULL_AUDIT;