暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

openGauss数据库源码解析系列文章——安全管理源码解析(一)

openGauss 2023-07-28
237

openGauss作为新一代自治安全数据库,提供了丰富的数据库基础安全能力,并逐步完善各类高阶安全能力。这些安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私保护等。本章节将围绕openGauss安全机制进行源码解读,以帮助数据库内核开发者在进行内核开发时正确地理解和使用安全功能接口,持续为产品提供安全保护能力,或基于当前安全能力进一步开发新的安全能力。

一、安全管理整体架构和代码概览

不同于数据库其他业务模块,安全管理模块并非逻辑集中的。安全管理模块中的安全能力是分散化的,在数据库整个业务逻辑的不同阶段提供对应的安全能力,从而构建数据库整体纵深安全防御能力。一个完整的安全管理整体架构如图1所示。

图1  openGauss安全机制体系

虽然整个安全机制是分散化的,但是每一个安全子模块都独立负责了一个完整的安全能力。如安全认证机制模块主要解决用户访问控制、登录通道安全问题;用户角色管理模块解决用户创建及用户权限管理问题。因此整体的安全管理体系架构的代码解读也将根据整个体系的划分来进行描述。

1. 认证机制

认证机制子模块在业务流程上主要包括认证配置文件管理、用户身份识别、口令校验等过程,其核心流程及接口定义如图2所示。

图2  openGauss安全认证代码接口

2. 用户角色管理

用户角色管理子模块在业务流程上主要包括角色创建、修改、删除、授权和回收。由于openGauss并未严格区分用户和角色,因此用户的管理与角色管理共用一套接口,仅在部分属性上进行区分。角色管理子模块涉及的功能及其对应的接口如图3所示。

图3  openGauss角色管理代码接口

3. 对象访问控制

对象访问控制子模块在业务流程上主要包括对象授权、对象权限回收以及实际对象操作时的对象权限检查,其核心流程及接口定义如图4所示。

图4  openGauss对象权限管理代码接口

4. 审计机制

审计机制子模块主要包括审计日志的创建和管理以及数据库的各类管理活动和业务活动的审计追溯。审计日志管理包括新创建审计日志、审计日志轮转、审计日志清理。审计日志追溯包括活动发生时的日志记录以及审计信息查询接口。其核心流程及接口定义如图5所示。

图5  openGauss审计线程(左)及审计日志记录(右)接口

二、安全认证

安全认证是数据库对外提供的第一道防线,数据库访问者只有完成身份识别、通过认证校验机制,才可以建立访问通道从事数据库管理活动。在整个安全认证过程中,涉及用户身份管理识别、用户口令安全存储以及完善的认证机制3大模块,而对于系统内部的进程间通信(主备),则需要调用业界通用的Kerberos认证机制,下面将主要围绕这4个子模块进行涉及原理介绍和代码解析。

2.1  身份认证

安全认证机制要解决的核心问题是谁可以访问数据库的问题。因此在定义身份时,除了描述访问用户,还要清晰定义整个过程中以何种方法访问、从何处访问、访问哪个数据库的问题,因此本小节重点介绍身份认证概念及源码。

身份认证是一个广义的概念,实际上定义了数据库系统的访问规则。openGauss的访问规则信息主要被记录在配置文件HBA(host-based authentication file,主机认证)中,HBA文件中的每一行代表一个访问规则,其书写格式如下:

    hostssl   DATABASE USER ADDRESS METHOD [OPTIONS]

    其中第1个字段代表套接字方法,第两个字段代表允许被访问的数据库,第3个字段代表允许被访问的用户,第4个字段代表允许访问的IP地址,第5个字段代表访问的认证方式,第6个字段则作为对第5个字段认证信息的补充。在定义访问规则时,需要按照访问的优先级来组织信息,对于访问需求高的规则建议写在前面。

    在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(internet protocol,互联网协议)地址信息 */
        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(secure sockets layer,安全套接层,工作于套接字层的安全协议。)认证信息*/
        #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(generic security service,通用安全服务)数据内容*/
           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(user identity,用户身份标识)信息获取当前系统用户名  */
                         (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_SSL
          if (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所示。

          表1  口令加密与认证方式

          password_encryption_type

          加密方式

          hash算法)

          认证方式

          pg_hba.conf

          加密函数接口

          0

          MD5

          MD5

          pg_md5_encrypt

          1

          SHA256 + MD5

          SHA256MD5

          calculate_encrypted_combined_password

          2(默认值)

          SHA256

          SHA256

          calculate_encrypted_sha256_password

          创建用户和修改用户属性的函数入口分别为CreateRole和AlterRole。在函数内对口令加密前会先校验是否满足口令复杂度,如果满足则调用calculate_encrypted_password函数实现口令的加密。加密时根据参数password_encryption_type配置选择对应的加密方式,加密完成后会清理内存中的敏感信息并返回口令密文。口令加密流程如图6所示。

          图6  口令加密流程图

          如图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:

            (1) StoredKey用来验证客户端用户身份。

            服务端认证客户端通过计算ClientSignature与客户端发来的ClientProof进行异或运算,从而恢复得到ClientKey,然后将其进行HMAC(hash-based message authentication code,散列信息认证码)运算,将得到的值与StoredKey进行对比,如果相等,证明客户端验证通过。其中ClientSignature通过StoredKey和token(随机数)进行HMAC计算得到。

            (2) ServerKey用来向客户端表明自己身份的。

            客户端认证服务端,通过计算ServerSignature与服务端发来的值进行比较,如果相等,则完成对服务端的认证。其中ServerSignature通过ServerKey和token(随机数)进行HMAC计算得到。

            (3) 在认证过程中,服务端可以计算出来ClientKey,验证完后直接丢弃不必存储。

            防止服务端伪造认证信息ClientProof,从而仿冒客户端。

            接下来详细描述在一个认证会话期间的客户端和服务端的信息交换过程。如图9所示。

            图9 openGauss认证流程

            认证流程为:

            (1) 客户端发送username。

            (2) 服务端返回盐值salt、iteration-count(迭代次数)、ServerSignature以及随机生成的字符串token给客户端。token是随机生成字符串。服务端通过计算得到的ServerSignature返回给客户端。

              ServerSignature := HMAC(ServerKey, token)

              (3) 客户端认证服务端并发送认证响应。响应信息包含客户端认证信息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)步接收的随机字符串发送给服务端进行认证。

                  (4) 服务端接收并校验客户端信息。

                  使用其保存的StoredKey和token通过HMAC算法进行计算,然后与客户端传来的ClientProof进行异或,恢复ClientKey;再对ClientKey进行哈希计算,得到的结果与服务端保存的StoredKey进行比较。如果相等则服务端对客户端的认证通过,否则认证失败。

                    ClientSignature := HMAC(StoredKey, token)
                    HMAC(ClientProof XOR ClientSignature ) = StoredKey

                    客户端认证的过程通过调用ClientAuthentication函数完成,该函数只有一个类型Port的参数,Port结构中存储着客户端相关信息,Port结构与客户端相关的部分字段参见“9.2.1  身份”章节介绍。完整的客户端认证过程见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);
                      }


                      /*  完成认证,关闭参数ImmediateInterruptOK  */
                      t_thrd.int_cxt.ImmediateInterruptOK = false;
                      }

                      在这个ClientAuthentication函数中通过先后调用hba_getauthmethod函数、check_hba函数,检查客户端地址、所连接数据库、用户名在文件HBA中是否有能匹配的HBA记录(具体HBA及check_hba相关内容参见“9.2.1  身份”节)。如果能够找到匹配的HBA记录,则将Port结构中相关认证方法的字段设置为HBA记录中的参数,同时状态值为STATUS_OK。然后根据不同的认证方法,进行相应的认证过程。具体认证方法如表9-2所示,在认证过程中可能需要和客户端进行多次交互。最后返回如果为STAUS_OK,则表示认证成功,并将认证成功的信息发送回客户端,否则发送认证失败的信息。

                      表2 认证方法


                      认证方法

                      描述

                      uaReject

                      0

                      无条件的拒绝连接

                      uaTrust

                      3

                      无条件的允许连接,即允许匹配HBA记录的客户端连入数据库

                      uaMD5

                      5

                      要求客户端提供一个MD5加密口令进行认证

                      uaSHA256

                      6

                      要求客户端提供SHA256加密口令进行认证

                      uaGSS

                      7

                      通过GSS-APIgeneric security service通用安全服务application programming interface,应用编程接口)认证用户

                      接下来介绍客户端认证服务端并发送认证响应。客户端根据不同的认证方法进行不同的处理过程,当前方法为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;
                                       }


                                       sha_bytes_to_hex64((uint8*)h, h_string);
                                       pwd_to_send = h_string; /* 设置要发送给服务端的值  */
                                   }
                               }
                        ……
                               break;
                        /*  清空变量  */
                        ……
                           return ret;
                        }

                        2.4  Kerberos安全认证

                        图10  Kerberos认证标准交互流程

                        其中各角色和定义如表3所示(为下文描述方便均以缩写代替)。

                        表3  Kerberos协议角色


                        认证方法

                        描述

                        uaReject

                        0

                        无条件的拒绝连接

                        uaTrust

                        3

                        无条件的允许连接,即允许匹配HBA记录的客户端连入数据库

                        uaMD5

                        5

                        要求客户端提供一个MD5加密口令进行认证

                        uaSHA256

                        6

                        要求客户端提供SHA256加密口令进行认证

                        uaGSS

                        7

                        通过GSS-APIgeneric security service通用安全服务application programming interface,应用编程接口)认证用户

                        openGauss可在数据库系统部署完毕之后开启Kerberos模式,即Kerberos服务部署在数据库系统机器上,部署过程中会开启Kerberos相关的服务,并派发凭证给集群内部所有的节点,初始化一系列Kerberos需要用到的环境变量,数据库内核中通过调用GSS-API来实现Kebreros标准协议的通信内容。以openGauss主备之间的认证为例,在Kerberos开启后openGauss内部进程之间认证流程如图11所示。

                        图11  数据库系统Kerberos认证流程

                        Kerberos提供用户(数据库管理员)透明的认证机制,数据库管理员无须感知Kerberos进程/部署情况。图11中分两部分描述Kerberos交互,左侧虚线框内的Kerberos协议实现部分由OM工具完成。OM工具在Kerberos初始化的时候将KDC服务拉起(krb5kdc进程),KDC服务内置了两个服务:AS和TGS服务。客户端(openGauss主备等数据库服务进程)在登录对端之前会先和KDC交互拿到TGT(ticket granting ticket,根凭证),这个步骤由OM拉起的定时任务调用Kerebros提供刷新票据工具来实现,默认24小时重新获取1次。该获取TGT的过程对应Kerberos标准协议中的AS-REQ、AS-REP、TGS-REQ和TGS-REP模块。

                        右侧侧虚线框内的数据库内侧认证,主要是图11右侧虚线框内的AP-REQ流程实现,简化流程如图12所示。

                        图12  数据库系统内核认证交互

                        数据库内核封装GSS-API数据结构,实现跟外部API交互认证,关键数据结构源代码文件为“src\include\libpq\auth.h”,相关代码如下:

                          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所示。认证流程如下。

                          (1) 服务端通过数据库配置文件决定使用Kerberos协议对客户端连接进行认证。

                          (2) 发起认证请求,客户端准备需要Kerberos认证的环境和票证,发’P’报文响应请求并发送票证。

                          (3) 服务端验证通过后会发送响应’R’报文,完成Kerberos认证。

                          小结:

                          本篇围绕安全管理整体架构和代码概览、安全认证原理介绍和代码解析进行简单介绍。下一篇将继续学习角色管理、对象权限管理,请持续关注!

                          文章转载自openGauss,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论