書接上文數據庫高安全—角色權限:權限管理&權限檢查,從權限管理和權限檢查方面解讀了高斯數據庫的角色權限,本篇將從傳統審計和統一審計兩方面對高斯數據庫的審計追蹤技術進行解讀。
4? 審計追蹤? ?
4.1 傳統審計
審計內容的記錄方式通常有兩種:記錄到數據庫的表中、記錄到OS文件中。openGauss采用記錄到OS文件中(即審計日志)的方式來保存審計結果,審計日志文件夾受操作系統權限保護,默認只有初始化用戶可以讀寫,從數據庫安全角度出發,保證了審計結果的可靠性。日志文件的存儲目錄由audit_directory參數指定。
openGauss審計日志每條記錄包括time、type、result、userid、username、database、client_conninfo、object_name、detail_info、node_name、thread_id、local_port、remote_port共13個字段。圖1為審計日志的單條記錄示例。
圖1? 審計記錄示例
對審計日志文件進行讀寫的函數主要位于pgaudit.cpp文件中,其中主要包括兩類函數:審計文件的讀、寫、更新函數;審計記錄的增、刪、查接口。
首先我們介紹審計文件的數據結構。
openGauss的審計日志采用文件的方式存儲在指定目錄中。通過查看目錄,我們發現日志主要包括兩類文件:形如0_adt的審計文件以及名為index_table索引文件。 ? ?
圖2 審計文件結構
以adt結尾的審計文件中,每一條審計記錄對應一個AuditData結構體。
數據結構AuditData:
typedef struct AuditData {
AuditMsgHdr header; // 記錄文件頭,存儲記錄的標識、大小等信息
AuditType type; // 審計類型
AuditResult result; // 執行結果
char varstr[1]; // 二進制格式存儲的具體審計信息
} AuditData;
其中AuditMsgHdr記錄著審計記錄的標識信息,其結構如下:
數據結構 AuditMsgHdr:
typedef struct AuditMsgHdr {
char signature[2]; // 審計記錄標識,目前固定為AUDIT前兩個字符’A’和’U’
uint16 version; // 版本信息,目前固定為0
uint16 fields; // 審計記錄字段數,目前為13
uint16 flags; // 記錄有效性標識,如果被刪除則標記為DEAD
pg_time_t time; // 審計記錄創建時間
uint32 size; // 審計信息占字節長度
} AuditMsgHdr;
AuditData的其他結構存儲著審計記錄的審計信息,AuditType為審計類型,目前有38種類型。AuditResult為執行的結果,有AUDIT_UNKNOWN、AUDIT_OK、AUDIT_FAILED三種結果。其余的各項信息,均通過二進制的方式寫入到varstr中。
審計日志有關的另一個文件為索引文件index_table,其中記錄著審計文件的數量、審計日志文件編號、審計文件修改日期等信息。 ? ?
數據結構 AuditIndexTable:
typedef struct AuditIndexTable {
uint32 maxnum; // 審計目錄下審計文件個數的最大值
uint32 begidx; // 審計文件開始編號
uint32 curidx; // 當前使用的審計文件編號
uint32 count; // 當前審計文件的總數
pg_time_t last_audit_time; // 最后一次寫入審計記錄的時間
AuditIndexItem data[1]; // 審計文件指針
} AuditIndexTable;
索引文件中每一個AuditIndexItem對應一個審計文件,其結構如下:
數據結構 AuditIndexTable:
typedef struct AuditIndexItem {
pg_time_t ctime; // 審計文件創建時間
uint32 filenum; // 審計文件編號
uint32 filesize; // 審計文件占空間大小
} AuditIndexItem;
審計文件的讀、寫類函數如auditfile_open、auditfile_rotate等函數實現較簡單,讀者可以直接閱讀源碼。
下面主要介紹日志文件的結構和日志記錄的增、刪、查接口。
審計記錄的寫入接口為audit_report函數。該函數的原型為:
void audit_report(AuditType type, AuditResult result, const char* object_name, const char* detail_info);
其中入參type、result、object_name、detail_info分別對應審計日志記錄中的相應字段,審計日志中的其余9個字段均為函數在執行時從全局變量中獲取。
audit_report函數的執行主要分為3個部分,首先會檢查審計的各項開關,判斷是否需要審計該操作。然后根據傳入的參數、全局變量中的參數以及當前時間,生成審計日志所需的信息并拼接成字符串。最后調用審計日志文件讀寫接口,將審計日志寫入文件中。
審計記錄查詢接口為pg_query_audit函數,該函數為數據庫內置函數,可供用戶直接調用,調用形式為:
SELECT * FROM pg_query_audit (timestamptz startime,timestamptz endtime, audit_log);
入參為需要查詢審計記錄的起始時間和終止時間以及審計日志文件所在的物理路徑。當不指定audit_log時,默認查看連接當前實例的審計日志信息。 ? ?
審計記錄的刪除接口為pg_delete_audit函數,該函數為數據庫內置函數,可供用戶直接調用,調用形式為:
SELECT * FROM pg_delete_audit (timestamptz startime,timestamptz endtime);
入參為需要被刪除審計記錄的起始時間和終止時間。該函數通過調用pgaudit_delete_file來將審計日志文件中,startime與endtime之間的審計記錄標記為AUDIT_TUPLE_DEAD,達到刪除審計日志的效果,而不實際刪除審計記錄的物理數據。也即執行該函數,審計日志文件大小不會減小。
4.2 統一審計
1. 執行原理
審計機制是openGauss的內置安全能力之一,openGauss提供對用戶發起的SQL行為審計和追蹤能力,支持針對DDL、DML語句和關鍵行為(登錄、登出、系統啟動、恢復)的審計。在每個工作線程初始化階段把審計模塊加載至線程中,其審計的執行原理是把審計函數賦給SQL生命周期不同階段的Hook,當線程執行至SQL處理流程的特定階段后會進行審計執行判定邏輯,審計模塊加載關鍵代碼如下:
void pgaudit_agent_init(void) {
…
// DDL、DML語句審計hook賦值, 賦值結束后標識審計模塊已在此線程加載
prev_ExecutorEnd = ExecutorEnd_hook;
ExecutorEnd_hook = pgaudit_ExecutorEnd;
prev_ProcessUtility = ProcessUtility_hook;
ProcessUtility_hook = (ProcessUtility_hook_type)pgaudit_ProcessUtility;
u_sess->exec_cxt.g_pgaudit_agent_attached = true;
}
SQL語句在執行到ProcessUtility_hook 和 ExecutorEnd_hook函數指針時,會分別進入到已預置好的審計流程中,這兩個函數指針的位置在SQL進入執行器執行之前,具體關系如圖3所示。 ? ?
圖3? 審計執行關系圖
如圖3所示,在線程初始化階段,審計模塊已加載完畢,SQL經過優化器得到計劃樹,此時審計模塊pgaudit_ExecutorEnd和pgaudit_ProcessUtility函數分別進行DML和DDL語句的分析,如果和已設置審計策略相匹配,則會調用審計日志接口,生成對應的審計日志,對于系統變更類的審計直接內置于相應行為的內核代碼中。
2. 關鍵執行流程
1) 系統變更類審計執行:
pgaudit_system_recovery_ok
pgaudit_system_start_ok
pgaudit_system_stop_ok
pgaudit_user_login
pgaudit_user_logout
pgaudit_system_switchover_ok
pgaudit_user_no_privileges
pgaudit_lock_or_unlock_user
以上為openGauss支持系統變更類的審計執行函數,對于此類審計函數均嵌入內核相應調用流程中,以審計用戶登入登出pgaudit_user_login為例說明其主體流程。 ? ?
圖4 登入審計執行流程
圖4為服務端校驗客戶端登入時的主要流程,以登錄失敗場景為例,首先根據配置文件和客戶端IP和用戶信息確認采用的認證方式(包括sha256和SSL認證等),然后根據不同的認證方式采用不同的認證流程和客戶端進行交互完成認證身份流程,如果認證失敗,則線程退出報錯給客戶端,pgaudit_user_login即在認證失敗的時候調用,獲取當前訪問數據庫名稱和詳細信息,調用審計日志接口記錄于審計日志中供審計管理員查看,關鍵代碼如下:
/* 拼裝登入口失敗時候的詳細信息,包括數據庫名稱和用戶名 */
rc = snprintf_s(details,
PGAUDIT_MAXLENGTH,
PGAUDIT_MAXLENGTH - 1,
"login db(%s)failed,authentication for user(%s)failed",
port->database_name,
port->user_name);
securec_check_ss(rc, "\0", "\0");
// 調用登入審計函數,記錄審計日志
pgaudit_user_login(FALSE, port->database_name, details);
// 退出當前線程
ereport(FATAL, (errcode(errcode_return), errmsg(errstr, port->user_name)))
登入審計日志接口pgaudit_user_login則主要完成審計日志記錄接口需要參數的拼接:
void pgaudit_user_login(bool login_ok, const char* object_name, const char* detaisinfo)
{
AuditType audit_type;
AuditResult audit_result;
Assert(detaisinfo);
// 審計類型和審計結果拼裝
if (login_ok) {
audit_type = AUDIT_LOGIN_SUCCESS;
audit_result = AUDIT_OK;
} else {
audit_type = AUDIT_LOGIN_FAILED;
audit_result = AUDIT_FAILED;
}
// 直接調用審計日志記錄接口
audit_report(audit_type, audit_result, object_name, detaisinfo);
}
2) DDL、DML語句審計執行
依據審計日志執行原理,DDL、DML語句的執行分別由于pgaudit_ProcessUtility、pgaudit_ExecutorEnd來承載,首先介紹函數pgaudit_ProcessUtility,其主體結構如下:
DDL審計執行函數關鍵入參parsetree用于識別審計日志類型(create/drop/alter等操作),入參queryString保存原始執行SQL語句,用于記錄審計日志,略去非關鍵流程,此函數主要根據判斷nodeTag所歸屬的DDL操作類型,進入不同的審計執行邏輯,以T_CreateStmt為例,識別當前語句create table則進入pgaudit_ddl_table邏輯進行審計日志執行并最終記錄審計日志。
圖5? DDL審計執行流程
如圖5所示,首先從當前SQL語句中獲取執行對象類別校驗其相應的審計開關是否開啟,當前支持開啟的全量對象如下,可以通過GUC參數audit_system_object控制:
typedef enum {
DDL_DATABASE = 0,
DDL_SCHEMA,
DDL_USER,
DDL_TABLE,
DDL_INDEX,
DDL_VIEW,
DDL_TRIGGER,
DDL_FUNCTION,
DDL_TABLESPACE,
DDL_RESOURCEPOOL,
DDL_WORKLOAD,
DDL_SERVERFORHADOOP,
DDL_DATASOURCE,
DDL_NODEGROUP,
DDL_ROWLEVELSECURITY,
DDL_TYPE,
DDL_TEXTSEARCH,
DDL_DIRECTORY,
DDL_SYNONYM
} DDLType;
如果DDL操作的對象審計已開啟則進行審計日志記錄流程,在調用審計日志記錄函數audit_report之前需要對包含密碼的SQL語句進行脫敏處理,即將包含密碼的語句中(create role/user)密碼替換成‘********’用于隱藏敏感信息,至此針對create DDL語句的審計執行完成,其他類型DDL語句主體流程一致,不做贅述。
下面介紹針對DML語句審計執行邏輯pgaudit_ExecutorEnd,整體調用流程如下圖6所示。
? ?
圖6 DML審計執行流程
首先判斷SQL查詢語句所歸屬的查詢類型,以CMD_SELECT類型為例,先獲取查詢對象的object_name用于審計日志記錄中訪問對象的記錄,然后調用pgaudit_dml_table:
case CMD_SELECT:
object_name = pgaudit_get_relation_name(queryDesc->estate->es_range_table);
pgaudit_dml_table_select(object_name, queryDesc->sourceText);
和DDL的記錄一樣,同樣會對敏感信息進行脫敏后調用審計日志記錄接口audit_report,DML審計日志執行完成。
以上內容從傳統審計和統一審計兩方面對高斯數據庫的審計追蹤技術進行解讀,下篇將從數據動態脫敏方面對高斯數據庫的數據保護技術進行解讀,敬請期待~