openGauss作为新一代自治安全数据库,提供了丰富的数据库基础安全能力,并逐步美满各类高阶安全能力。这些安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私掩护等。本章节将围绕openGauss安全机制举行源码解读,以帮助数据库内核开辟者在举行内核开辟时正确的理解和利用安全功能接口,连续为产品提供安全掩护能力,或基于当前安全能力进一步开辟新的安全能力。
1 openGauss安全团体架构
不同于数据库其他业务模块,安全管理模块并非逻辑集中的。安全管理模块中的安全能力是分散化的,在数据库整个业务逻辑的不同阶段提供对应的安全能力,从而构建数据库团体纵深安全防御能力。一个完整的安全管理团体架构如图1下所示:
图1 openGauss安全机制体系
虽然整个安全机制是分散化的,但是每一个安全子模块都独立负责了一个完整的安全能力。如安全认证机制模块重要办理用户访问控制,登录通道安全问题;用户脚色管理模块办理用户创建及用户权限管理问题。因此团体的安全管理体系架构的代码解读也将根据整个体系的划分来举行描述。
认证机制子模块从业务流程上看重要包括认证配置文件管理、用户身份辨认、口令校验等过程,其核心流程及接口界说如图2所示。
图2 openGauss安全认证代码接口
用户脚色管理子模块从业务流程上看重要包括脚色创建、修改、删除、授权和接纳,由于openGauss并未严格区分用户和脚色,因此用户的管理与脚色管理共用一套接口,仅在部分属性上举行区分。脚色管理子模块涉及的功能及其对应的接口如图3所示。
图3 openGauss脚色管理代码接口
对象访问控制子模块从业务流程看重要包括对象授权、对象权限接纳以及实际对象操作时的对象权限检查,其核心流程及接口界说如图4所示。
图4 openGauss对象权限管理代码接口
审计机制子模块重要包括审计日志的创建和管理,以及数据库的各类管理运动和业务运动的审计追溯。审计日志管理包括新创建审计日志、审计日志轮转、审计日志清理。审计日志追溯包括运动发生时的日志记录以及审计信息查询接口,其核心流程及接口界说如图5所示。
图5 openGauss审计线程(左)及审计日志记录(右)接口
2 安全认证
安全认证是数据库对外提供的第一道防线,数据库访问者只有完成身份辨认,通过认证校验机制,才可以创建访问通道从事数据库管理运动。在整个安全认证过程中,涉及到用户身份管理辨认、用户口令安全存储以及美满的认证机制3大模块,下面的小节将围绕这3个子模块举行涉及原理介绍和代码剖析。
2.1 身份标识
安全认证机制要办理的核心问题是谁可以访问数据库的问题。因此在界说身份时,除了描述谁,还要清楚界说整个过程中哪个用户以何种方法访问、从那边访问,访问哪个数据库的问题,因此本小节重点介绍身份辨认概念及源码。
身份辨认是一个广义的概念,实际上界说了数据库系统的访问规则。openGauss的访问规则信息重要被记录在配置文件HBA(Host-Based Authentication File,主机认证)中,HBA文件中的每一行代表一个访问规则,其誊写格式如下:
- hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
复制代码 其中第1列代表套接字方法,第2列代表允许被访问的数据库,第3列代表允许被访问的用户,第4列代表允许访问的IP地址,第5列代表访问的认证方式,第6列则作为对第五列认证信息的增补。在界说访问规则时,必要按照访问的优先级来组织信息。对于访问需求高的规则建议写在前面。
在openGauss源码中,界说了存储访问规则的关键数据布局HbaLine,核心元素如下所示:
- typedef struct HbaLine{ int linenumber; // 规则行号 ConnType conntype; // 连接套接字方法 List* databases; // 允许访问的数据库集合 List* roles; // 允许访问的用户组… char* hostname; // 允许访问的IP地址 UserAuth auth_method; // 认证方法…} HbaLine;
复制代码 其中,字段conntype,database,roles,hostname以及auth_method分别对应HBA配置文件中的套接字方法、允许被访问的数据库、允许被访问的用户,IP地址以及当前该规则的认证方法。
当系统管理员配置完HBA文件后,配置文件被存放在数据库服务端侧。当某个用户通过数据库用户发起认证请求时,毗连相关的信息都存放在关键数据布局Port中,如下所示:
- typedef struct Port {…SockAddr laddr; // 本地进程IP地址信息SockAddr raddr; // 远端客户端进程IP地址信息char* remote_host; // 远端host名称字符串或IP地址char* remote_hostname; // 可选项,远程host名称字符串或IP地址 … // 发送给backend的数据包信息,包括访问的数据库名称,用户名,配置参数char* database_name;char* user_name;char* cmdline_options;List* guc_options; // 认证相关的配置信息HbaLine* hba;… // SSL认证信息#ifdef USE_SSL SSL* ssl; X509* peer; char* peer_cn; unsigned long count;#endif… // Kerberos认证数据结构信息#ifdef ENABLE_GSS char* krbsrvname; // Kerberos服务进程名称 gss_ctx_id_t gss_ctx; // GSS数据内容 gss_cred_id_t gss_cred; // 凭证信息 gss_name_t gss_name; gss_buffer_desc gss_outbuf; // GSS token信息#endif} Port;
复制代码 其中Port布局中的user_name、database_name、raddr以及对应的hba等字段就是认证相关的用户信息、访问数据库信息以及IP地址信息。与此同时Port布局中还包含了SSL认证相关的信息以及节点间做Kerberos认证相关的信息。有了Port信息,背景服务线程会根据前端传入的信息与HbaLine中记录的信息逐一比较,完成对应的身份辨认。完整的身份标识过程见函数check_hba(),其核心逻辑如下所示:
- /**扫描hba文件,寻找匹配连接请求的规则项 */static void check_hba(hbaPort* port){ …… // 获取当前连接用户的id roleid = get_role_oid(port->user_name, true);
- foreach (line, t_thrd.libpq_cxt.parsed_hba_lines) { hba = (HbaLine*)lfirst(line); // 认证连接行为分为本地连接行为和远程连接行为,需分开考虑 if (hba->conntype == ctLocal) { // 对于local套接字,仅允许初始安装用户本地登录 if (roleid == INITIAL_USER_ID) { char sys_user[SYS_USERNAME_MAX + 1];…… // 基于本地环境的uid信息获取当前系统用户名 (void)getpwuid_r(uid, &pwtmp, pwbuf, pwbufsz, &pw); ……
- // 记录当前系统用户名 securec_check(strncpy_s(sys_user,SYS_USERNAME_MAX+1, pw->pw_name, SYS_USERNAME_MAX), "\0", "\0");
- // 对于访问用户与本地系统用户不相匹配的场景,均需提供密码 if (strcmp(port->user_name, sys_user) != 0) hba->auth_method = uaSHA256; } else if (hba->auth_method == uaTrust) { hba->auth_method = uaSHA256; }…… } else { // 访问行为为远端访问行为,需要逐条判断包括认证方式在内的信息正确性 if (IS_AF_UNIX(port->raddr.addr.ss_family)) continue; // SSL连接请求套接字判断#ifdef USE_SSLif (port->ssl != NULL) { if (hba->conntype == ctHostNoSSL) continue; } else { if (hba->conntype == ctHostSSL) continue; }#else if (hba->conntype == ctHostSSL) continue;#endif // IP白名单校验 switch (hba->ip_cmp_method) { case ipCmpMask: if (hba->hostname != NULL) { if (!check_hostname(port, hba->hostname)) continue; } else { if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr, (struct sockaddr*)&hba->mask)) continue; } break; case ipCmpAll: break; case ipCmpSameHost: case ipCmpSameNet: if (!check_same_host_or_net(&port->raddr, hba->ip_cmp_method)) continue; break; default: /* shouldn't get here, but deem it no-match if so */ continue; } } /* != ctLocal */
- // 校验数据库信息和用户信息 if (!check_db(port->database_name, port->user_name, roleid, hba->databases)) continue; if (!check_role(port->user_name, roleid, hba->roles)) continue; …… port->hba = hba; return; }
- // 没有匹配则拒绝当前连接请求 hba = (HbaLine*)palloc0(sizeof(HbaLine)); hba->auth_method = uaImplicitReject; port->hba = hba;}
复制代码 2.2 口令存储
口令是安全认证过程中的重要根据。openGauss数据库在实行创建用户或修改用户口令操作时,会将口令通过单向哈希方式加密后存储在pg_authid系统表中。口令加密的方式与参数password_encryption_type的配置有关,现在系统支持md5、sha256 + md5(同时存储sha256和md5哈希值)和sha256三种方式,默认采用sha256方式加密。为兼容PostgreSQL社区和第三方工具,openGauss保留了md5方式,此方式安全性较低不保举用户利用。
口令的加密方式与认证方式密切相关,选择不同的加密方式必要对应的修改pg_hba.conf中的认证方式。口令加密与认证方式对应关系如下表所示:
表1. 口令加密与认证方式
password_encryption_type
| 加密方式
(hash算法)
| 认证方式
(pg_hba.conf)
| 加密函数接口
| 0
| md5
| md5
| pg_md5_encrypt
| 1
| sha256 + md5
| sha256或md5
| calculate_encrypted_combined_password
| 2(默认值)
| sha256
| sha256
| calculate_encrypted_sha256_password
| 创建用户和修改用户属性的函数入口分别为CreateRole和AlterRole。在函数内对口令加密前,会先校验是否满意口令复杂度,如果满意则调用calculate_encrypted_password函数实现口令的加密。加密时根据参数password_encryption_type配置选择对应的加密方式,加密完成后会清理内存中的敏感信息并返回口令密文。口令加密流程如下图6所示:
图6 口令加密流程图
如图9-6所示,通过调用calculate_encrypted_sha256_password函数实现sha256加密方式,通过调用pg_md5_encrypt函数实现md5方式,而calculate_encrypted_combined_password函数则融合了前面两种加密方式,加密后系统表中包含了sha256和md5两种哈希值。实现sha256加密的calculate_encrypted_sha256_password函数实行流程如图7所示。
图7 calculate_encrypted_sha256_password函数实行流程
2.3 认证机制
整个认证过程中,身份标识完成后,必要完成最后的认证辨认。通过用户名和暗码来验证数据库用户的身份,判定其是否为合法用户。openGauss利用基于RFC5802协议的口令认证方案,该方案是一套包含服务器和客户端双向认证的用户认证机制。
起首,客户端知道用户名username和暗码password,客户端发送用户名username给服务端,服务端检索相应的认证信息,比方:salt、StoredKey、ServerKey和迭代次数。然后,服务端发送盐值salt和迭代次数给客户端。接下来,客户端必要举行一些计算,给服务端发送ClientProof认证信息,服务端通过ClientProof对客户端举行认证,并发送ServerSignature给客户端。最后,客户端通过ServerSignature对服务端举行认证。具体秘钥计算如下所示:
- SaltedPassword := Hi(password, salt, iteration_count) 其中,Hi()本质上是PBKDF2。ClientKey := HMAC(SaltedPassword, "Client Key")StoredKey := sha256(ClientKey)ServerKey := HMAC(SaltedPassword, "Sever Key")ClientSignature:=HMAC(StoredKey, token)ServerSignature:= HMAC(ServerKey, token)ClientProof:= ClientSignature XOR ClientKey
复制代码 具体秘钥衍生过程如图8所示。
图8 秘钥衍生过程
服务器端存的是StoredKey和ServerKey:
StoredKey是用来验证客户端用户身份。
服务端认证客户端通过计算ClientSignature与客户端发来的ClientProof举行异或运算,从而恢复得到ClientKey,然后将其举行HMAC(Hash-based Message Authentication Code,散列信息认证码)运算,将得到的值与StoredKey举行对比,如果相等,证实客户端验证通过。其中ClientSignature通过StoredKey和token(随机数)举行HMAC计算得到。
ServerKey是用来向客户端表明自己身份的。
类似的,客户端认证服务端,通过计算ServerSignature与服务端发来的值举行比较,如果相等,则完成对服务端的认证。其中ServerSignature通过ServerKey和token(随机数)举行HMAC计算得到。
在认证过程中,服务端可以计算出来ClientKey,验证完后直接抛弃不必存储。
防止服务端伪造认证信息ClientProof,从而仿冒客户端。
接下来详细描述在一个认证会话期间的客户端和服务端的信息交换过程。如图9所示:
图9 openGauss认证流程
客户端发送username。
服务端返回盐值salt、iteration-count(迭代次数)、ServerSignature以及随机生成的字符串token给客户端。token是随机生成字符串。服务端通过计算得到的ServerSignature返回给客户端。
- ServerSignature := HMAC(ServerKey, token)
复制代码 客户端认证服务端并发送认证相应。相应信息包含客户端认证信息ClientProof。ClientProof证实客户端拥有ClientKey,但是不通过网络的方式发送。在收到信息后,计算ClientProof。
客户端利用salt和iteration-count,从password计算得到SaltedPassword,然后通过图9中的公式计算得到ClientKey、 StoryKey和ServerKey。
客户端通过StoredKey和token举行哈希计算得到ClientSignature:
- ClientSignature := HMAC(StoredKey,token)
复制代码 通过将ClientKey和ClientSignature举行异或得到ClientProof:
- ClientProof := ClientKey XOR ClientSignature
复制代码 将计算得到的ClientProof和第2步接收的随机字符串发送给服务端举行认证。
服务端接收并校验客户端信息。
利用其保存的StoredKey和token通过HMAC算法举行计算,然后与客户端传来的ClientProof举行异或,恢复ClientKey,再对ClientKey举行哈希计算,得到的结果与服务端保存的StoredKey举行比较。如果相等,则服务端对客户端的认证通过。否则,认证失败。
- ClientSignature := HMAC(StoredKey, token)
- HMAC(ClientProof XOR ClientSignature ) = StoredKey
复制代码
客户端认证的过程通过调用ClientAuthentication函数完成,该函数只有一个类型Port的参数,Port布局中存储着客户端相关信息。完整的客户端认证过程见函数ClientAuthentication (),如下所示:
- void ClientAuthentication(Port* port){ int status = STATUS_ERROR; char details[PGAUDIT_MAXLENGTH] = {0}; char token[TOKEN_LENGTH + 1] = {0}; errno_t rc = EOK; GS_UINT32 retval = 0;hba_getauthmethod(port);…… switch (port->hba->auth_method) { case uaReject:……case uaImplicitReject: ……// 使用MD5口令认证case uaMD5: sendAuthRequest(port, AUTH_REQ_MD5); status = recv_and_check_password_packet(port); break;// 使用sha256认证方法case uaSHA256: // 禁止使用初始用户进行远程连接 if (isRemoteInitialUser(port)) { ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("Forbid remote connection with initial user."))); } rc = memset_s(port->token, TOKEN_LENGTH * 2 + 1, 0, TOKEN_LENGTH * 2 + 1); securec_check(rc, "\0", "\0"); HOLD_INTERRUPTS(); // 生成随机数token retval = RAND_priv_bytes ((GS_UCHAR*)token, (GS_UINT32)TOKEN_LENGTH); RESUME_INTERRUPTS(); CHECK_FOR_INTERRUPTS(); if (retval != 1) { ereport(ERROR, (errmsg("Failed to Generate the random number,errcode:%u", retval))); } sha_bytes_to_hex8((uint8*)token, port->token); port->token[TOKEN_LENGTH * 2] = '\0'; // 发送认证请求到前端,认证码为AUTH_REQ_SHA256 sendAuthRequest(port, AUTH_REQ_SHA256); // 接收并校验客户端的信息 status = recv_and_check_password_packet(port); break;……}……if (status == STATUS_OK) sendAuthRequest(port, AUTH_REQ_OK);else { auth_failed(port, status);}
- // 完成认证,关闭参数ImmediateInterruptOKt_thrd.int_cxt.ImmediateInterruptOK = false;}
复制代码 在这个ClientAuthentication函数中,通过调用函数hba_getauthmethod,然后调用check_hba函数,检查客户端地址、所毗连数据库、用户名在文件HBA中是否有能匹配的HBA记录。如果能够找到匹配的HBA记录,则将Port布局中相关认证方法的字段设置为HBA记录中的参数,同时状态值为STATUS_OK。然后,根据不同的认证方法,举行相应的认证过程。具体认证方法如表2。在认证过程中大概必要和客户端举行多次交互。最后返回如果为STAUS_OK,则表示认证乐成,并将认证乐成的信息发送回客户端,否则发送认证失败的信息。
表2 认证方法
认证方法
| 值
| 描述
| uaReject
| 0
| 无条件的拒绝毗连
| uaTrust
| 3
| 无条件的允许毗连,这个方法允许被该HBA记录匹配的客户端直接连入数据库。
| uaMD5
| 5
| 要求客户端提供一个MD5加密口令举行认证。
| uaSHA256
| 6
| 要求客户端提供SHA256加密口令举行认证。
| uaGSS
| 7
| 通过GSSAPI认证用户。
| 接下来介绍客户端认证服务端并发送认证相应。客户端根据不同的认证方法举行不同的处置惩罚过程,当火线法为AUTH_REQ_SHA256时,通过调用函数pg_password_sendauth完成对服务端的认证,如下所示:
- static int pg_password_sendauth(PGconn* conn, const char* password, AuthRequest areq){int ret;// 初始化变量…… char h[HMAC_LENGTH + 1] = {0}; char h_string[HMAC_LENGTH * 2 + 1] = {0}; char hmac_result[HMAC_LENGTH + 1] = {0}; char client_key_bytes[HMAC_LENGTH + 1] = {0}; switch (areq) { case AUTH_REQ_MD5: // pg_md5_encrypt()通过MD5Salt进行MD5加密。…… case AUTH_REQ_MD5_SHA256:…… case AUTH_REQ_SHA256: { char* crypt_pwd2 = NULL; if (SHA256_PASSWORD == conn->password_stored_method || PLAIN_PASSWORD == conn->password_stored_method) { // 通过sha256方式加密密码 if (!pg_sha256_encrypt( password, conn->salt, strlen(conn->salt), (char*)buf, client_key_buf, conn->iteration_count)) return STATUS_ERROR;
- rc = strncpy_s(server_key_string, sizeof(server_key_string), &buf[SHA256_LENGTH + SALT_STRING_LENGTH], sizeof(server_key_string) - 1); securec_check_c(rc, "\0", "\0"); rc = strncpy_s(stored_key_string, sizeof(stored_key_string), &buf[SHA256_LENGTH + SALT_STRING_LENGTH + HMAC_STRING_LENGTH], sizeof(stored_key_string) - 1); securec_check_c(rc, "\0", "\0"); server_key_string[sizeof(server_key_string) - 1] = '\0'; stored_key_string[sizeof(stored_key_string) - 1] = '\0';
- sha_hex_to_bytes32(server_key_bytes, server_key_string); sha_hex_to_bytes4(token, conn->token);// 通过server_key和token调用HMAC算法计算,得到client_server_signature_bytes,通过该变量转为字符串变量,用来验证与服务端传来的server_signature是否相等。 CRYPT_hmac_ret1 = CRYPT_hmac(NID_hmacWithSHA256, (GS_UCHAR*)server_key_bytes, HMAC_LENGTH, (GS_UCHAR*)token, TOKEN_LENGTH, (GS_UCHAR*)client_server_signature_bytes, (GS_UINT32*)&hmac_length); if (CRYPT_hmac_ret1) { return STATUS_ERROR; } sha_bytes_to_hex64((uint8*)client_server_signature_bytes, client_server_signature_string);
- // 调用函数strncmp判断计算的client_server_signature_string和服务端传来的server_signature值是否相等 if (PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_GAUSS_BASE && 0 != strncmp(conn->server_signature, client_server_signature_string, HMAC_STRING_LENGTH)) { pwd_to_send = fail_info; // 不相等,则认证失败 } else { sha_hex_to_bytes32(stored_key_bytes, stored_key_string); // 通过stored_key和token计算得到hmac_result CRYPT_hmac_ret2 = CRYPT_hmac(NID_hmacWithSHA256, (GS_UCHAR*)stored_key_bytes, STORED_KEY_LENGTH, (GS_UCHAR*)token, TOKEN_LENGTH, (GS_UCHAR*)hmac_result, (GS_UINT32*)&hmac_length);
- if (CRYPT_hmac_ret2) { return STATUS_ERROR; }
- sha_hex_to_bytes32(client_key_bytes, client_key_buf);// hmac_result和client_key_bytes异或得到h,然后将其发送给服务端,用于验证客户端 if (XOR_between_password(hmac_result, client_key_bytes, h, HMAC_LENGTH)) { return STATUS_ERROR; }
复制代码 2.4 Kerberos安全认证
Kerberos是一种基于对称秘钥技术的身份认证协议,开源组件Kerberos可以办理集群内节点大概进程之间的认证问题,即当开启kerberos之后,恶意用户无法仿冒集群内节点或进程来登录数据库系统,只有内部组件才可以持有效于认证的根据,从而保证通过Kerberos认证,消减了仿冒风险,提升了数据库系统的安全性。Kerberos协议具体交互如图10所示。
图10 Kerberos认证标准交互流程
其中各脚色和界说如下,为下文描述方便,均以缩写取代:
表3 Kerberos协议脚色
KDC(Key Distribution Center)
| Kerberos服务步伐
| Client
| 必要访问服务的用户(principal),KDC和Service会对用户的身份举行认证
| Service
| 集成了Kerberos的服务,被访问的服务,必要对客户端举行认证
| AS(Authentication Service,认证服务)
| AS服务器用于身份的校验, 内部会存储所有的账号信息
| TGS(Ticket Granting Service,票据授权服务)
| TGT(Ticket-Granting Ticket)票据分发服务
| openGauss可在数据库系统摆设完毕之后开启Kerberos模式,即Kerberos服务摆设在数据库系统机器上,摆设过程中会开启Kerberos相关的服务,并派发根据给集群内部所有的节点,初始化一系列Kerberos必要用到的环境变量,数据库内核中通过调用GSS-API来实现Kebreros标准协议的通信内容,下面给出在Kerberos开启后openGauss内部进程之间认证流程,以openGauss主备之间的认证为例:
图11 数据库系统Kerberos认证流程
Kerberos提供用户(数据库管理员)透明的认证机制,数据库管理员无需感知Kerberos进程/摆设情况。分两部分描述Kerberos交互,左侧虚线框内的Kerberos协议实现部分由OM(Operations Management,运维管理模块)工具完成,OM工具在Kerberos初始化的时候将KDC服务拉起(krb5kdc进程),其内置了两个服务:AS和TGS服务,客户端(openGauss主备等数据库服务进程)在登录对端之前会先和KDC交互拿到TGT(Ticket Granting Ticket,根根据),这个步调由OM拉起的定时任务调用Kerebros提供刷新票据工具来实现,默认24小时重新获取1次。这个获取TGT的过程对应Kerberos标准协议中AS-REQ、AS-REP、TGS-REQ和TGS-REP。
在数据库内核侧,重要是图12右侧虚线框内的AP-REQ流程实现,简化流程如图12所示。
图12 数据库系统内核认证交互
数据库内核封装GSS-API提供数据布局和API实现认证交互,关键数据布局如下:
- typedef struct GssConn {
- int sock;
- gss_ctx_id_t gctx; // GSS 上下文
- gss_name_t gtarg_nam; // GSS 名称
- gss_buffer_desc ginbuf; // GSS 输入token
- gss_buffer_desc goutbuf; // GSS 输出token
- } GssConn;
- // 客户端、服务端接口, 用于封装标准kerberos协议调用, 其中客户端接口用于向服务端
- // 发起访问,同时响应服务端接口GssServerAuth发起的票据请求
- int GssClientAuth(int socket, char* server_host);
- int GssServerAuth(int socket, const char* krb_keyfile);
复制代码
图13 数据库内核Kerberos认证时序图
具体交互逻辑时序如图13所示。
- 起首服务端通过数据库配置文件决定利用Kerberos协议对客户端毗连举行认证。
- 然后发起认证请求,客户端准备必要Kerberos认证的环境和票证,发’P’报文相应请求并发送票证。
- 服务端验证通过后会发送相应’R’报文,完成Kerberos认证。
以上内容从安全团体架构与安全认证两方面,对高斯数据库的高安全性能举行了详细解读,下篇我们将从脚色权限方面继续介绍高斯数据库的高安全技术,敬请期待~
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |