-
安全认证
1、背景:opengauss作为安全数据库,可能面临隐私泄露、信息篡改、数据丢失等风险。为了防止恶意攻击者访问、窃取、篡改和破坏数据库中的数据,阻止未经授权用户通过一些系统的漏洞进行仿冒、提权等路径恶意使用数据库,opengauss提供了一些列安全措施。这一小节主要对认证机制进行详细的分析。
2、认证机制的主要功能:
(1)身份认证
-
- 定义:定义了数据库系统的访问规则
- 主要解决问题:以何种方法访问、从何处访问、访问哪个数据。
- 主要的数据结构为HbaLine以及Port:
- 定义:定义了数据库系统的访问规则
-
-
- 核心代码:check_hba函数
-
上述是一个简化的流程图,主要的流程是遍历HBA规则列表,检查连接类型、SSL状态、IP地址、数据库和用户信息等,以确定是否允许连接。
- 口令存储
-
- 定义:口令是安全认证过程中的重要凭证。opengauss数据库每在创建用户和修改用户时,会将口令通过一定方式单项哈希加密否存放在pg_authid系统表中。
- 主要用的加密方式有以下四种:口令加密与认证方式对应关系:
-
password_encryption_type | 加密方法 | 加密函数接口 |
0 | MD5 | pg_md5_encrypt |
1 | SHA256+MD5 | calculate_encrypted_combined_password |
PASSWORD_TYPE_SM3 | SM3 | gs_calculate_encrypted_sm3_password |
除上述的所有情况 | SH256 | calculate_encrypted_sha256_password |
加密时候,根据参数password_encryption_type配置选择对应的加密方式,加密完成后会清理内存中的敏感信息。
- 核心代码:
创建用户和修改用户属性的函数入口分别是CreteRole和AlterRole。在函数内对口令加密前会先检验是否满足口令的复杂度,如果满足则调用calculate_encrypted_password函数实现口令的加密。
- 认证机制
- 背景:整个认证过程中身份认证完成后需要完成最后的认证识别。通过用户名和密码来验证数据库用户的身份,判断其是否为合法用户。openGauss使用基于RFC5802协议的口令认证方案,该方案是一套包含服务器和客户端双向认证的用户认证机制。
- 核心代码1:客户端认证的过程通过调用ClientAuthentication函数完成
认证方法 | 值 | 描述 |
uaReject | 0 | 无条件的拒绝连接 |
uaImplicitReject | 3 | 无条件的允许连接,即允许匹配HBA记录的客户端连入数据库 |
uaGSS | 7 | 通过GSS-API(通用安全服务;application programming interface)认证用户 |
基于选择的方法的实际身份验证 | / | 具体身份验证逻辑,如uaMD5,uaSHA256,uaSM3等等 |
在函数ClientAuthentication函数中先后调用hba_getauthmethod函数、check_hba函数,检查客户端地址、所连数据库、用户名在文件HBA中是否能有匹配的HBA记录。
-
- 核心函数2:hba_getauthmethod函数
-
- check_hba_replication函数:
’check_hba_replication‘函数的主要功能是检查walsender进程的认证是否符合HBA规则,并根据一系列条件设置认证方法。最终,将匹配的'HbaLine'复制到当前连接的HBA规则,并设置当前连接的认证方法。
-
- 核心函数3:check_hba函数
'check_hba'函数的主要功能是根据用户连接的信息(包括用户名、角色等)检查和是否满足HBA规则,并根据规则设置相应的认证方法。
- 亮点分析和总结:
- 身份验证模块:
- 身份验证是数据库安全的基石,openGauss在源码中实现了多种身份验证方法,包括MD5、SHA256、SM3等;
- 身份验证过程涉及密码哈希、安全连接(SSL/TLS)等方面的实现。
- HBA规则处理:
- HBA规则用于定义数据库的客户端如何进行身份验证;
- 源码中通过解析"pg_hba.conf"文件,根据配置的规则来判断是否允许连接;
- 提供了对本地连接和远程连接的细粒度控制,包括对主机、IP地址、SSL状态等条件的匹配;
- 权限管理:
- openGauss实现了丰富的权限管理机制,包括对表、列、模式等级别的权限控制;
- 通过'GRANT'和‘REVOKE’语句,数据库管理员可以细致地控制用户对数据库对象的访问权限
- IAM
- openGauss引入了IAM模块,将安全认证与身份管理结合起来;
- IAM提供了对角色、用户的综合管理,同时支持对LDAP等外部身份源的接入。
- SSL/TLS支持:
- 通过引入SSL/TLS支持,openGauss保障了数据传输的安全性;
- 支持SSL/TLS的客户端身份验证,提供了一种更加安全的连接方式。
- 审计和日志:
- openGauss实现了审计和日志功能,可以记录数据库中发生的关键时间;
- 通过审计,管理员可以追踪用户操作、登录尝试,帮助发现和应对潜在的安全威胁;
- 密码策略
- 提供了对密码的复杂性要求,可以通过配置密码策略来强制用户使用更安全的密码;
- 集成第三方身份验证方法:
- openGauss支持集成第三方身份验证方式,比如Kerberos、LDAP等,使得用户可以利用企业中已有的身份认证系统。
- 身份验证模块:
-
角色管理
在OpenGauss数据库中,角色管理是构建安全高效权限体系的关键组成部分。角色作为拥有数据库对象和权限的实体,可以是用户、组,或两者兼具。这一管理系统包含角色的创建、修改、删除,以及权限的授予和回收等操作。这一小节主要围绕这四方面进行详细的分析。
- 角色创建
- 相关数据结构
与角色创建的相关数据结构为CreateRoleStmt,DefElem和RoleStmtType,CreateRoleStmt的代码和相关注释如下:
其中,DefElem节点代表一个角色创建语句中可以包含的附加信息或配置项,在处理创建角色语句时,可以遍历 options 链表,处理每个 DefElem 节点,从而实现对不同选项的解析和处理,其代码如下:
RoleStmtType是一个枚举类型,表示不同角色类型,分别是角色、用户和用户组,其相关代码如下:
- 相关流程图
openGauss创建角色时,主要调用了CreateRole函数,其实现的流程图如下:
- 相关内容解析
- openGauss首先会根据类型的不同默认情况下分配不同的权限,当创建角色是用户时,由于用户通常用于代表具体的人或应用程序,是真正需要登录的实体,因此默认情况下数据库允许用户登录(canlogin为ture);而当创建的类型是角色和组时,通常它们用于更好地管理和组织权限,而不是为了让本身登录,因而设置为不允许登录(canlogin为false)可能更符合通常的使用场景。
- 在创建一个新的数据库角色时,可以根据实际需求指定为角色指定多个选项,以下是一些常用的角色创建选项解释:
option选项 | 释义 |
SUPERUSER | NOSUPERUSER | 角色是否是超级用户 |
CREATEDB | NOCREATEDB | 角色是否有创建数据库的权限 |
CREATEROLE | NOCREATEROLE | 角色是否有创建其他角色的权限 |
INHERIT | NOINHERIT | 角色是否继承权限 |
LOGIN | NOLOGIN | 角色是否允许登录数据库 |
REPLICATION | NOREPLICATION | 角色是否允许进行复制 |
PERSISTENCE | TEMPORARY | 角色是持久性的还是临时的 |
RESOURCE POOL respool_name | 角色关联的资源池 |
CONNECTION LIMIT connlimit | 角色的最大并发连接数限制 |
同时,数据库确保了这些选项在语法上和语义上是合法的,并在不合法或冲突的情况下报错。同时,它也负责在多次指定这些选项时进行清理,以确保选项的一致性和正确性。 比如数据库在处理创建角色时涉及密码的一些选项时:
-
- 在完成对选项的处理后,数据库将这些选项转换成内部表现形式,并将结果储存在new_record的相对应位置,然后判断用户的角色类型,记录在相关字段中,在源代码对用户角色类型的判断过程时,也会进行相应的安全处理,进行兼容性检测:
-
- 在完成以上等操作后,代码将会把新建角色的记录(相关选项属性)插入到pg_authid表中,pg_authid表是存储用户身份信息的系统表之一,其中包含了关于用户角色的各种属性。最后将新角色添加到现有角色以及添加指定的成员到新角色中,即完成了角色的创建。
- 修改角色
- 相关数据结构
与角色修改的相关数据结构是AlterRoleStmt,其代码和相关注释如下:
- 下面将围绕这些数据结构的相关变量在修改角色属性时发挥的作用进行分析
- missing_ok是一个布尔值,表示如果要修改的角色不存在时是否跳过错误。如果为 true,则在角色不存在时不会报错,而是会跳过执行,在代码中主要用在了获取角色的元组(tuple)失败时进行的处理:
数据库这样设计主要是提供了一种在某些情况下允许操作失败,而在其他情况下继续执行的机制,提高操作的灵活性和容错性。
-
- option的操作和处理和创建角色差不多,通过一个循环提取出选项,并根据并根据选项的值更新相应的状态变量,最后更新到新的元组中。
- action变量表示要对角色执行的操作,+1表示要执行添加成员的操作,如果是-1则表示要执行删除成员的操作,用在修改角色最后一部分进行:
-
- lockstatus变量表示角色的锁定状态,用于指定在修改角色期间是否对其进行锁定,在代码中用于执行锁定或解锁操作时:
锁定角色是指将用户账户设置为不可用或暂时禁止登录的状态。通常是为了应对一些安全性问题,例如在检测到多次登录失败(可能是密码破解尝试)后,为了防止暴力攻击。
在热备模式下(即数据库的备用节点,可能是一个只读的从节点),发生一些异常情况时,会通过锁定角色来防止非法访问或者进一步损害数据库的安全性。
解锁角色是指取消对用户账户的锁定状态,允许用户正常登录和使用数据库。在热备模式下,解锁角色可能是因为在主节点上进行了一些修复或者安全性检查,因此需要允许该角色重新访问数据库。
- 删除角色
- 相关数据结构
- 其核心函数DropRole的大致流程如下:
删除操作完成后,进行资源的清理,同时保持锁定状态直到提交,这样可以确保在事务完成之前其他事务不能访问或修改已锁定的资源,维护了事务的隔离性,防止并发执行的事务之间发生不一致的情况。
- 授予和回收角色
- 相关数据结构
与该功能有关的数据结构有GrantRoleStmt,相关的代码和注释如下:
- 下面将主要针对AddRoleMems 进行分析
- 介绍
AddRoleMems 函数是用于向指定角色添加成员的关键函数之一。该函数主要用于处理角色和成员之间的关联关系,管理角色成员的添加,并执行相应的权限检查,通过对角色关系的合法性进行详细的权限检查和成员关系检查,确保了数据库安全性和一致性。
-
- 函数签名以及参数说明
参数名 | 说明 |
rolename | 角色名称 |
roleid | 角色的对象标识符(OID) |
memberNames | 待添加的成员角色名称列表 |
memberIds | 待添加的成员角色的对象标识符(OID)列表 |
grantorId | 执行授权的角色的对象标识符(OID) |
admin_opt | 是否包含管理员选项 |
-
- 主要逻辑
- 权限检查
- 主要逻辑
- 首先,函数会检查待添加的成员角色列表是否为空,如果是则直接返回,避免不必要的权限检查和操作
- 接下来,对于预定义角色,必须具有对要更改的预定义角色的管理员选项。如果执行者缺少管理员选项,则会报错。
- 只有持久化用户自己或初始用户可以将自己添加为其他成员。其他情况下,必须具有 createrole 权限或指定角色的管理员选项。
- 对于操作员角色、超级用户角色、系统管理员角色,以及独立用户的成员关系的修改有特定的权限要求。
-
-
- 成员关系检查
-
- 函数会检查是否存在成员关系循环,包括一个角色成为自己的成员的情况。如果存在循环,会通过报错信息指示拒绝创建这样的成员关系。
-
-
- 更新成员关系
-
- 对于每个待添加的成员,函数会检查是否已经存在相应的成员关系条目。如果已经存在并且没有添加管理员选项,则会发出警告。
- 如果不存在相应的成员关系,函数会构建一个新的元组并插入到 pg_auth_members 表中。
- 在插入或更新后,函数会检查是否存在数据库处于逻辑集群环境中,并确保授予的角色和成员具有相同的节点组ID,否则会报错。
-
SQL解析
SQL引擎负责对用户输入的SQL语言进行编译,生成可执行的执行计划,然后将执行计划交给执行引擎进行执行。SQL引擎整个编译的过程如图1所示,在编译的过程中需要对输入的SQL语言进行词法分析、语法分析、语义分析,从而生成逻辑执行计划,逻辑执行计划经过代数优化和代价优化之后,产生物理执行计划。
(1) 词法分析:从查询语句中识别出系统支持的关键字、标识符、操作符、终结符等,确定每个词自己固有的词性。常用工具如flex。
(2) 语法分析:根据SQL语言的标准定义语法规则,使用词法分析中产生的词去匹配语法规则,如果一个SQL语句能够匹配一个语法规则,则生成对应的抽象语法树(abstract synatax tree,AST)。常用工具如Bison。
(3) 语义分析:对抽象语法树进行有效性检查,检查语法树中对应的表、列、函数、表达式是否有对应的元数据,将抽象语法树转换为查询树。
openGuass中SQL解析代码流程如下所示:
其中词法结构和语法结构分别由scan.l和gram.y文件定义,并通过flex和bison分别编译成scan.cpp和gram.cpp文件。SQL解析的相关源文件如下表所示:
源文件 | 说明 |
src/common/backend/parser/scan.l | 定义词法结构,采用Lex编译后生成scan.cpp文件 |
src/common/backend/parser/gram.y | 定义语法结构,采用Yacc编译后生成gram.cpp文件 |
src/common/backend/parser/scansup.cpp | 提供词法分析的常用函数 |
src/common/backend/parser/parser.cpp | 词法、语法分析的主入口文件,入口函数是raw_parser |
src/common/backend/parser/analyze.cpp | 语义分析的主入口文件,入口函数是parse_analyze |
完成语义分析后,SQL解析过程完成,SQL引擎开始执行查询优化。
模块 | 目录 | 说明 |
查询重写 | src/gausskernel/optimizer/prep | 主要包括子查询优化、谓词化简及正则化、谓词传递闭包等查询重写优化技术 |
统计信息 | src/gausskernel/optimizer/commands/analyze.cpp | 生成各种类型的统计信息,供选择率估算、行数估算、代价估算使用 |
代价估算 | src/common/backend/utils/adt/selfuncs.cpp src/gausskernel/optimizer/path/costsize.cpp | 进行选择率估算、行数估算、代价估算 |
物理路径 | src/gausskernel/optimizer/path | 生成物理路径 |
动态规划 | src/gausskernel/optimizer/plan | 通过动态规划方法对物理路径进行搜索 |
遗传算法 | src/gausskernel/optimizer/geqo | 通过遗传算法对物理路径进行搜索 |
(4)查询重写
查询重写就是把用户输入的SQL语句转换为更高效的等价SQL,查询重写遵循两个基本原则。
- 等价性:原语句和重写后的语句,输出结果相同。
- 高效性:重写后的语句,比原语句在执行时间和资源使用上更高效。
(5)动态规划
目前openGauss已经完成了基于规则的查询重写优化和逻辑分解优化,并且已经生成各个基表的物理路径。基表的物理路径仅仅是优化器规划中很小的一部分,现在openGauss将进入优化器优化的另一个重要的工作,即生成Join连接路径。openGauss采用的是自底向上的优化方式,对于多表连接路径主要采用的是动态规划和遗传算法两种方式。这里主要介绍动态规划的方式,但如果表数量有很多,就需要用遗传算法。遗传算法可以避免在表数量过多情况下带来的连接路径搜索空间膨胀的问题。对于一般场景采用动态规划的方式即可,这也是openGauss默认采用的优化方式。
(6)遗传算法
区别于动态规划将问题分解成若干独立子问题求解的方法,遗传算法是一个选择的过程,它通过将染色体杂交构建新染色体的方法增大解空间,并在解空间中随时通过适应度函数进行筛选,推举良好基因,淘汰掉不良的基因。这就使得遗传算法获得的解不会像动态规划一样,一定是全局最优解,但可以通过改进杂交和变异的方式,来争取尽量的靠近全局最优解。
得益于在多表连接中的效率优势,在openGauss数据库中,遗传算法是动态规划方法的有益补充。只有在Enable_geqo参数打开,并且待连接的RelOptInfo的数量超过Geqo_threshold(默认12个)的情况下,才会使用遗传算法。