深度解讀RDS for MySQL 審計日誌功能和原理

华为云开发者联盟發表於2024-10-29

本文分享自華為雲社群《【華為雲MySQL技術專欄】RDS for MySQL 審計日誌功能介紹》,作者:GaussDB資料庫。

1. 背景

在生產環境中,當資料庫出現故障或問題時,運維人員需要快速定位出異常或者高危的SQL語句。這時,審計日誌能夠提供詳細的記錄,幫助追蹤每個資料庫操作的執行者、執行時間以及受影響的資料物件,從而大大加速故障排查和恢復流程。

MySQL企業版提供了審計日誌外掛,可以對資料庫操作進行細粒度的審計。該外掛支援記錄使用者登入、查詢執行、資料修改等重要操作。然而,在MySQL社群版中,只是提供了審計日誌的相關外掛介面定義和功能描述,並不支援原生的審計日誌功能。

為了彌補這一功能的缺失,華為雲RDS for MySQL透過整合Percona公司開源的審計日誌外掛,實現了MySQL審計日誌功能。該功能已在RDS for MySQL 5.7和RDS for MySQL 8.0版本中開放,滿足了使用者對資料庫安全審計的需求,同時增強了資料庫的合規性和可用性。

本文將以RDS for MySQL為研究物件,對於審計日誌進行功能介紹和原理解析。

2. 功能引數介紹

當在RDS for MySQL上開啟審計日誌功能,使用者可以透過SHOW variables LIKE 'audit%';語句檢視與審計日誌功能相關的變數名和引數值。

mysql> SHOW variables LIKE 'audit%';
+-------------------------------------+---------------+
| Variable_name                       | Value         |
+-------------------------------------+---------------+
| audit_log_anonymized_ip             |               |
| audit_log_buffer_size               | 1048576       |
| audit_log_csv2_escape               | OFF           |
| audit_log_csv2_old_separated_format | OFF           |
| audit_log_csv2_truncation           | ON            |
| audit_log_exclude_accounts          |               |
| audit_log_exclude_commands          |               |
| audit_log_exclude_databases         |               |
| audit_log_file                      | audit.log     |
| audit_log_flush                     | OFF           |
| audit_log_force_rotate              | OFF           |
| audit_log_format                    | CSV2          |
| audit_log_handler                   | FILE          |
| audit_log_include_accounts          |               |
| audit_log_include_commands          |               |
| audit_log_include_databases         |               |
| audit_log_policy                    | ALL           |
| audit_log_rotate_on_size            | 104857600     |
| audit_log_rotations                 | 50            |
| audit_log_strategy                  | ASYNCHRONOUS  |
| audit_log_syslog_facility           | LOG_USER      |
| audit_log_syslog_ident              | percona-audit |
| audit_log_syslog_priority           | LOG_INFO      |
+-------------------------------------+---------------+
23 rows in set (0.01 sec)

這些引數控制著審計日誌外掛的整體功能,允許使用者靈活調節審計日誌的各個方面。透過合理設定和調整這些引數,使用者可以精確確定日誌的記錄範圍、記錄級別、儲存方式等。例如,使用者可以透過audit_log_policy 決定審計日誌的記錄級別,也可以透過改變audit_log_strategy 調整審計日誌的重新整理策略。

在 RDS for MySQL中,部分變數因安全合規考慮未開放修改。下表對審計日誌功能中相關變數的作用和預設值進行介紹。

表格 1 審計日誌變數介紹

3. 日誌內容解析

在RDS for MySQL上,audit_log_policy的預設值為ALL。在該級別下,審計日誌會記錄包括DML(資料操作語言)、DDL(資料定義語言)、DCL(資料控制語言)操作以及連線或斷開連線等資料庫活動。需要注意的是,不同型別的活動包含的日誌欄位有所不同。下面將從常見DDL、DML、DCL操作以及資料庫連線與斷開連線產生的審計日誌進行內容解析。

DDL、DML和DCL

對於DDL、DML和DCL操作,所生成的審計日誌格式是相同的。對於日誌欄位的具體含義詳見如下。

  • RECORD ID:審計日誌唯一ID,用來標識每條審計日誌。

  • STATUS:狀態碼,非0表示ERROR。

  • NAME:操作命令分類,QUERY、EXECUTE、QUIT、CONNECT等。

  • TIMESTAMP:記錄日誌發生的時間戳。

  • COMMAND_CLASS:記錄DDL、DML和DCL操作的型別,SELECT、INSERT、DELETE等。

  • SQLTEXT:執行SQL語句的內容。

  • USER:連線資料庫的使用者名稱。

  • HOST:連線資料庫的主機名。

  • IP:連線資料庫的客戶端IP地址。

  • DATABASE:連線指定的資料庫名。

Connect和Disconnect

連線或斷開連線事件,在使用者登入成功或登入失敗時均會有日誌記錄。與DDL和DDL操作產生的審計日誌不同,連線事件產生審計日誌增加了PRIV_USER、OS_LOGIN等欄位,下面對於連線或斷開連線產生的審計日誌進行解析。

  • RECORD ID: 審計日誌唯一ID,用來標識每條審計日誌。

  • STATUS:狀態碼,非0表示ERROR。

  • NAME: 操作命令分類,QUERY、EXECUTE、QUIT、CONNECT等。

  • TIMESTAMP:記錄日誌發生的時間戳

  • USER:連線資料庫的使用者名稱。

  • PRIV_USER:經過身份驗證的使用者名稱。

  • OS_LOGIN:外部使用者名稱。

  • PROXY_USER:代理使用者名稱。

  • HOST:連線資料庫的主機名。

  • IP:連線資料庫的客戶端IP地址。

  • DATABASE:連線指定的資料庫名。

透過對審計日誌內容的解析,使用者不僅可以快速地檢視任意時間段資料庫的活動狀態,還能夠準確瞭解每條SQL語句的詳情,包括執行的使用者、時間戳、查詢型別等關鍵資訊。這樣詳細的記錄為安全審查、問題排查以及效能最佳化提供了強有力的支撐。

4.RDS for MySQL審計日誌原理淺析

RDS for MySQL審計功能的核心是透過不同型別的事件驅動審計日誌外掛完成對應型別日誌的記錄。在RDS for MySQL中一共支援兩類事件,即一般事件和連線事件。一般事件可以理解為使用者執行的DDL、DML和DCL語句。連線事件則是連線資料庫(Connect)和斷開連線(Disconnect)資料庫。審計日誌外掛支援事件定義的相關程式碼如下。

static int is_event_class_allowed_by_policy(mysql_event_class_t event_class,num audit_log_policy_t policy) {
  static unsigned int class_mask[] = {
      /* ALL */
      (1 << MYSQL_AUDIT_GENERAL_CLASS) | (1 << MYSQL_AUDIT_CONNECTION_CLASS),
      0,                                   /* NONE */
      (1 << MYSQL_AUDIT_CONNECTION_CLASS), /* LOGINS */
      (1 << MYSQL_AUDIT_GENERAL_CLASS),    /* QUERIES */
  };

  return (class_mask[policy] & (1 << event_class)) != 0;
}

當發生可審計事件時,伺服器會呼叫相關的審計介面,以便向已註冊的審計日誌外掛傳遞該事件的資訊,確保審計外掛在必要時能夠接收到並處理該事件。

審計日誌功能在RDS for MySQL核心的入口函式是mysql_audit_notify。透過對應事件驅動審計日誌外掛的工作。主要工作流程呼叫棧如下所示。

  do_command
    ->dispatch_commnad
        ->mysql_audit_notify
            ->event_class_dispatch
                  // 檢查當前外掛是否需要處理此事件
                ->plugin_dispatch
                    // 按事件類別下發審計任務
                    ->audit_log_notify

資料庫核心在收到一條SQL的執行請求後,首先,會透過do_command函式處理該連線。處理完成後,由dispatch_command函式依據不同SQL型別進行命令分發。之後,審計入口函式mysql_audit_notify會完成審計日誌記錄前的準備和校驗工作。如果校驗透過,則後續工作會由已註冊的審計日誌外掛中的其他函式完成。

透過函式呼叫棧可以看出,審計日誌功能與資料庫核心之間實現了高度解耦。二者透過預先註冊的函式介面進行對接,這種設計提高了未來功能擴充套件的靈活性。

審計日誌外掛的校驗和資源準備工作由mysql_audit_acquire_plugins函式完成,當該函式完成校驗後,即審計日誌外掛已完成註冊並且相關資源也已完成繫結,接下來將由audit_log_notify函式按照事件的型別和相關引數設定完成任務分發。audit_log_notify會呼叫audit_log_write函式完成審計日誌的寫入,audit_log_write會依據audit_log_handler變數的值來判斷是寫入審計日誌檔案還是系統日誌。

若是寫入日誌檔案中,此時還會判斷日誌的寫入策略;如果audit_log_strategy是PERFORMANCE或ASYNCHRONOUS,則會呼叫audit_handle_file_write_buf函式,將日誌內容寫入審計日誌外掛的緩衝區中,否則會呼叫audit_handle_file_write_nobuf函式,將日誌內容直接寫入作業系統檔案快取。

若audit_log_handler值為SYSLOG,則意味著審計日誌會直接寫到系統日誌中。那麼則會透過呼叫audit_handler_syslog_write完成日誌向系統日誌的寫入。審計日誌內部函式呼叫流程如圖所示。

圖1 審計日誌外掛工作流程圖

從圖中可以看到,審計日誌的落盤方式主要有兩種,分別是透過檔案系統完成日誌落盤和利用審計日誌刷盤執行緒不斷地將緩衝區的日誌寫入磁碟中。審計日誌的緩衝區是在審計日誌外掛初始化時完成相關資源的分配,其結構體如下:

struct audit_log_buffer {
  // 緩衝區內容
  char *buf;
  // 緩衝區大小
  size_t size;
  // 寫日誌位置
  size_t write_pos;
  // 日誌落盤位置
  size_t flush_pos;
  // 日誌落盤工作執行緒
  pthread_t flush_worker_thread;
  // 緩衝區是否暫停
  int stop;
  // 緩衝區滿是否丟棄該日誌
  int drop_if_full;
  void *write_func_data;
  audit_log_write_func write_func;
  mysql_mutex_t mutex;
  mysql_cond_t flushed_cond;
  mysql_cond_t written_cond;
  log_record_state_t state;
};

從審計日誌緩衝區結構體可以看到,日誌緩衝區主要透過日誌寫入函式和日誌落盤執行緒完成其核心功能。

當日志需要寫入緩衝區時,首先會比較日誌的長度和緩衝區的大小。如果審計日誌的長度大於日誌緩衝區的大小,並且緩衝區滿且選擇不丟棄該日誌時,審計日誌的落盤執行緒暫停工作,並會繞過日誌的緩衝區,直接寫入檔案緩衝區中。當審計日誌長度小於日誌緩衝區大小時,此時會將日誌的內容複製到檔案緩衝區中,並更新緩衝區write_pos等相關引數,等待日誌落盤執行緒的工作。如果當前寫入位置超過整個緩衝區大小的一半,則會立刻通知落盤執行緒,完成審計日誌的落盤。

審計日誌的落盤工作主要由日誌落盤工作執行緒完成。如果日誌緩衝區沒有關閉並且緩衝區中還存在日誌尚未落盤,則會迴圈呼叫日誌落盤函式進行日誌寫入。

static void *audit_log_flush_worker(void *arg) {
  audit_log_buffer_t *log = (audit_log_buffer_t *)arg;
  // 執行緒初始化
  my_thread_init();
  // 如果日誌緩衝區沒有關閉並且當前還有日誌未落盤
  while (!(log->stop && log->flush_pos == log->write_pos)) {
    // 進行日誌的落盤工作
    audit_log_flush(log);
  }
  // 關閉執行緒
  my_thread_end();

  return nullptr;
}

對於日誌落盤函式,會透過迴圈判斷write_pos和flush_pos是否相等,如果二者相等並且日誌緩衝區沒有停止工作,此時會等待1秒進入迴圈;如果二者不相等,說明緩衝區中有新的日誌需要落盤。

此時,若write_pos大於緩衝區的大小,日誌外掛會把flush_pos後的所有日誌進行落盤,並將當前日誌的狀態設為LOG_RECORD_INCOMPLETE。若write_pos在緩衝區大小範圍內,則會將該條日誌完整寫入審計日誌檔案中並將該日誌狀態設為LOG_RECORD_COMPLETE。在完成日誌落盤操作後,與緩衝區相關變數值會同步更新,併為下次日誌落盤做好準備。

5.使用說明

1)資料庫例項開啟審計日誌

登入管理控制檯,在雲資料庫RDS for MySQL的“例項管理”頁面,單擊目標例項名稱,進入基本資訊頁面。在左側導航欄,點選“SQL審計”,在“SQL審計”右側點選開啟按鈕,在彈框中點選“確定”,開啟審計日誌開關。

圖2 設定審計日誌功能

2)審計日誌的下載

開啟審計日誌後,資料庫的相關活動都會以日誌的形式記錄在OBS中,使用者可以在控制檯介面進行審計日誌的下載。

圖3 控制檯介面下載審計日誌

6.總結

RDS for MySQL的審計日誌功能在使用者活動監控、許可權變更追蹤和效能最佳化等方面有著重要的作用。它不僅幫助企業提升資料庫的整體安全性,滿足日益嚴格的合規性要求,還在故障排查中提供有價值的資訊。這一功能能夠精確的記錄使用者的資料庫操作,有助於識別潛在的安全威脅,併為效能瓶頸分析和最佳化提供詳實的資料支援。

華為開發者空間,匯聚鴻蒙、昇騰、鯤鵬、GaussDB、尤拉等各項根技術的開發資源及工具,致力於為每位開發者提供一臺雲主機、一套開發工具及雲上儲存空間,讓開發者基於華為根生態創新。

點選連結,免費領取您的專屬雲主機~

相關文章