opengauss数据库源码解析系列文章——审计与追踪(代码片段)

Gauss松鼠会 Gauss松鼠会     2023-01-26     622

关键词:

在前面文章中介绍过“9.4 对象权限管理”,本篇我们介绍第9章 安全管理源码解析中“9.5 审计与追踪”的相关精彩内容介绍。
审计机制和审计追踪机制能够对用户的日常行为进行记录和分析,实现规避风险、提高安全性。

9.5.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个字段。图9-23为审计日志的单条记录示例。

图9-23 审计记录示例

对审计日志文件进行读写的函数的代码主要位于“pgaudit.cpp”文件中,其中主要包括两类函数:审计文件的读、写、更新函数;审计记录的增、删、查接口。
首先介绍审计文件的数据结构,如图9-24所示。
openGauss的审计日志采用文件的方式存储在指定目录中。通过查看目录,可以发现日志主要包括两类文件:形如0_adt的审计文件以及名为index_table索引文件。

图9-24 审计文件结构

以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对应一个审计文件,其数据结构AuditIndexItem的代码如下:

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,达到删除审计日志的效果,而不实际删除审计记录的物理数据。带来的效果是执行该函数审计日志文件大小不会减小。

9.5.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进入执行器执行之前,具体关系如图9-25所示。

图9-25 审计执行关系图

如图9-25所示,在线程初始化阶段审计模块已加载完毕。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函数为例说明其主体流程。

图9-26 登入审计执行流程

图9-26为服务端校验客户端登入时的主要流程。以登录失败场景为例,首先根据配置文件和客户端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语句审计执行

依据“1. 执行原理”节的描述,DDL、DML语句的执行分别由于pgaudit_ProcessUtility函数、pgaudit_ExecutorEnd函数来承载。此处首先介绍函数pgaudit_ProcessUtility函数,其主体结构代码如下:

static void pgaudit_ProcessUtility(Node* parsetree, const char* queryString, ...)

/*  适配不同编译选项  */
...
/*  开始匹配不同的DDL语句  */
switch (nodeTag(parsetree)) 
    case T_CreateStmt: 
        /* CREATE table语句审计执行 */
        CreateStmt* createtablestmt = (CreateStmt*)(parsetree);
        pgaudit_ddl_table(createtablestmt->relation->relname, queryString);
     break;
    case T_AlterTableStmt: 
        AlterTableStmt* altertablestmt = (AlterTableStmt*)(parsetree); /* Audit alter table */
        if (altertablestmt->relkind == OBJECT_SEQUENCE) 
            pgaudit_ddl_sequence(altertablestmt->relation->relname, queryString);
         else 
            pgaudit_ddl_table(altertablestmt->relation->relname, queryString);
        
     break;
    /*  匹配其他DDL类型语句逻辑  */
    ...


DDL审计执行函数关键入参parsetree用于识别审计日志类型(create/drop/alter等操作)。入参queryString保存原始执行SQL语句,用于记录审计日志,略去非关键流程。此函数主要根据判断nodeTag所归属的DDL操作类型,进入不同的审计执行逻辑。以T_CreateStmt为例,识别当前语句CREATE table则进入pgaudit_ddl_table逻辑进行审计日志执行并最终记录审计日志。

图9-27 DDL审计执行流程

如图9-27所示,首先从当前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函数,整体调用流程如图9-28所示。

图9-28 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语句的审计日志执行完成。

感谢大家学习第9章 安全管理源码解析中“9.5 审计与追踪”的精彩内容,下一篇我们开启“9.6 数据安全技术”的相关内容的介绍。

敬请期待。

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...aceUpdate更新模式,中文意思为:原地更新,是openGauss内核新增的一种存储模式。openGauss内核 查看详情

opengauss数据库源码解析系列文章——ai技术之“指标采集预测与异常检测”(代码片段)

上一篇介绍了“8.4智能索引推荐”的相关内容,本篇我们介绍“8.5指标采集、预测与异常检测”的相关精彩内容介绍。8.5指标采集、预测与异常检测数据库指标监控与异常检测技术,通过监控数据库指标,并基于时序... 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...储引擎,针对众核和大内存服务器进行了优化。MOT是openGauss数据库的一个先进特性& 查看详情

⭐opengauss数据库源码解析系列文章——deepsql⭐(代码片段)

...另外一个大方向,即DB4AI。在本章中,我们将介绍openGauss的 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...绍。4.2.5行存储索引机制本节以B-Tree索引为例,介绍openGauss中行存储(格式)表的索引机制。索引本质上是对数据的一种物理有序聚簇。有序聚簇参考的排序字段被称 查看详情

opengauss数据库源码解析系列文章——数据安全技术(上)(代码片段)

...理源码解析中“9.6数据安全技术”的相关精彩内容介绍。openGauss采用了多种加密解密技术来提升数据在各个环节的安全性。9.6.1数据加解密接口用户在使用数据库时,除了需要基本的数据库安全之外,还会对导入的数据进... 查看详情

⭐opengauss数据库源码解析系列文章——对象权限管理⭐(代码片段)

...。9.4对象权限管理权限管理是安全管理重要的一环,openGauss权限管理基于访问控制列表(accesscontrollist,ACL&#x 查看详情

opengauss数据库源码解析系列文章——事务机制源码解析(代码片段)

上一篇为介绍完"5.1事务整体架构和代码概览"及“5.2事务并发控制”,本篇将继续介绍“5.3锁机制”的精彩内容。5.3锁机制数据库对公共资源的并发控制是通过锁来实现的,根据锁的用途不同,通常可以分为3种&... 查看详情

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

...查询优化”及“6.4小结”的相关内容的介绍。6.3查询优化openGauss数据库的查询优化过程功能比较明晰,从源代码组织的角度来看,相关代码分布在不同的目录下,如表6-6所示。表6-6查询优化模块说明模块目录说明查询... 查看详情

opengauss数据库源码解析系列文章——执行器解析(代码片段)

...线式的执行,对底层的存储引擎中的数据进行操作。openGauss数据库将执行的过程抽象成了不同类型的算子,同时结合编 查看详情

⭐opengauss数据库源码解析系列文章——ai查询时间预测⭐(代码片段)

上一篇介绍了“8.5指标采集、预测与异常检测”的相关内容,本篇我们介绍“8.6AI查询时间预测”的相关精彩内容介绍。8.6AI查询时间预测在前面介绍过“慢SQL发现”特性,该特性的典型场景是新业务上线前的检查,... 查看详情

⭐opengauss数据库源码解析系列文章——角色管理⭐(代码片段)

❤️‍大家好,我是Gauss松鼠会,欢迎进来学习啦~❤️‍在前面介绍过“9.1安全管理整体架构和代码概览、9.2安全认证”,本篇我们介绍第9章安全管理源码解析中“9.3角色管理”的相关精彩内容介绍。9.3角色管理角... 查看详情

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

本篇我们开启“SQL引擎源解析”中“6.1概述”及“6.2SQL解析”的精彩内容介绍。第6章SQL引擎源解析SQL引擎作为数据库系统的入口,主要承担了对SQL语言进行解析、优化、生成执行计划的作用。对于用户输入的SQL语句,SQL... 查看详情

opengauss数据库源码解析系列文章——执行器解析(代码片段)

上一篇介绍了第七章执行器解析中“7.4表达式计算”及“7.5编译执行”的相关内容,本篇将介绍“7.6向量化引擎”及“7.7小结”的精彩内容。7.6向量化引擎传统的行执行引擎大多采用一次一元组的执行模式,这样在执行... 查看详情

opengauss数据库源码解析系列文章——ai技术(代码片段)

上一篇介绍了第七章执行器解析中“7.6向量化引擎”及“7.7小结”的相关内容,本篇我们开启第八章AI技术中“8.1概述”及“8.2自调优”的相关精彩内容介绍。AI技术最早可以追溯到20世纪50年代,甚至比数据库系统的发展... 查看详情

tensorflow源码解析系列文章索引

文章索引framework解析resourceallocatortensoropnodekernelgraphdevicefunctionshape_inferencecommon_runtime解析devicesessiongraph_optimizerexecutor-1executor-2direct_session后记关于起源阅读tensorflow源码时,为了敦促自己主动思考,把阅读的笔 查看详情

数据库迁移系列从oracle迁移到opengauss实战分享(代码片段)

之前的迁移系列中我们介绍了Mysql到openGauss的迁移方法,本篇介绍使用Ora2og工具从Oracle到openGauss数据库的迁移。文章目录简介迁移前准备环境软件安装ora2og工具安装创建迁移项目配置ora2pg.conf测试迁移导出导入Ora2Pg不足FAQ简介... 查看详情

数据库迁移系列从mysql到opengauss的数据库对象迁移实践(代码片段)

在之前这一篇中我们分享过使用chameleon工具完成MySQL到openGauss的全量数据复制、实时在线复制。9.30新发布的openGauss3.1.0版本,工具的全量迁移和增量迁移的性能不但有了全面提升,而且支持数据库对象视图、触发器、自定... 查看详情