diff --git "a/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\217\243\344\273\244\345\255\230\345\202\250.md" "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\217\243\344\273\244\345\255\230\345\202\250.md"
new file mode 100644
index 0000000000000000000000000000000000000000..5e297f676dd17f7332cd5ccb32273c333aaa3540
--- /dev/null
+++ "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\217\243\344\273\244\345\255\230\345\202\250.md"
@@ -0,0 +1,520 @@
+### openGauss安全机制之口令存储
+
+[TOC]
+
+#### 前言
+
+本文《openGauss安全机制之口令存储》为第五届中国软件开源创新大赛——开源代码评注赛道参赛作品
+
+作者:袁建硕
+
+所属战队:共同学习
+
+时间:2022-10-02
+
+评注代码:[opengauss-mirror/openGauss-server: openGauss kernel (github.com)](https://github.com/opengauss-mirror/openGauss-server)
+
+***
+
+
+
+#### 口令加密与存储概述
+
+口令的加密与存储作为身份认证的重要一环,尤其是在面向庞大的价值数据面前,安全性与可靠性格外重要。本文主要分析openGauss对口令的操作与加密方式,并另外重点分析新增支持的SM3加密算法原理。
+
+openGauss数据库在执行创建用户或修改用户口令操作时,会将口令通过**单向Hash**加密存储在pg_authid系统表中。口令的加密方式与加密参数配置在"password_encryption_type"中。
+
+目前系统支持MD5、SHA256+MD5(同时存储两种加密值)、SHA256以及**SM3(新增)**四种方式,默认采用第三种SHA256加密方式。
+
+> 这里解释为何要保留已经稍显落伍的MD5加密方式?
+>
+> 主要是考虑到了数据库体系结构的**向后兼容**,为了**兼容已经逐渐成熟的PostgreSQL**社区及其他工具,保留了安全性较低的MD5方式。
+
+
+
+一张表概括各类加密类型、加密方式、认证方式、加密函数之间的关系:
+
+| password_encryption_type | 加密方式 | 认证方式(pg_hba.conf) | 加密函数接口 |
+| ------------------------ | ---------- | --------------------- | -------------------------------------- |
+| 0 | MD5 | MD5 | pg_md5_encrypt |
+| 1 | SHA256+MD5 | SHA256或MD5 | calculate_encrypted_combinaed_password |
+| 2(默认) | SHA256 | SHA256 | calculate_encrypted_sha256_password |
+| 3(PASSWORD_TYPE_SM3) | SM3 | SM3 | gs_calculate_encrypted_sm3_password |
+
+
+
+#### 口令加密过程
+
+口令加密的需求产生于**创建用户以及用户修改密码**。收到指令后,生成随机数作为**盐值Salt**。之后**检查**口令是否满足**复杂度的要求**(详情可见[补充](#补充)部分),满足口令复杂度的话,则去**执行口令加密函数**,**判断加密方式**进行加密,返回密文口令,**存储到pg_authid系统表**中,完成加密的全过程。
+
+
+
+
+
+#### 相关代码分析
+
+##### 口令加密函数 → calculate_encrypted_password
+
+```cpp
+/* gausskernel/optimizer/commands/user.cpp:6052-6092 */
+Datum calculate_encrypted_password(bool is_encrypted, const char* password, const char* rolname,
+ const char* salt_string)
+{
+ // 空密码
+ if (password == NULL || password[0] == '\0') {
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("The password could not be NULL.")));
+ }
+ errno_t rc = EOK;
+ char encrypted_md5_password[MD5_PASSWD_LEN + 1] = {0};
+ Datum datum_value;
+
+ // 是否已加密
+ if (!is_encrypted || isPWDENCRYPTED(password)) {
+ return CStringGetTextDatum(password);
+ }
+
+ /*
+ * The guc parameter of u_sess.attr.attr_security.Password_encryption_type here may be 0, 1, 2.
+ * if Password_encryption_type is 0, the encrypted password is md5.
+ * if Password_encryption_type is 1, the encrypted password is sha256 + md5.
+ * if Password_encryption_type is 2, the encrypted password is sha256.
+ * 另外新增了了SM3的加密方式Password_encryption_type is PASSWORD_TYPE_SM3(其值为3)
+ */
+ // MD5加密
+ if (u_sess->attr.attr_security.Password_encryption_type == 0) {
+ if (!pg_md5_encrypt(password, rolname, strlen(rolname), encrypted_md5_password)) {
+ rc = memset_s(encrypted_md5_password, MD5_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + 1);
+ securec_check(rc, "\0", "\0");
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("password encryption failed")));
+ }
+
+ datum_value = CStringGetTextDatum(encrypted_md5_password);
+ rc = memset_s(encrypted_md5_password, MD5_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + 1);
+ securec_check(rc, "\0", "\0");
+ ereport(NOTICE, (errmsg("The encrypted password contains MD5 ciphertext, which is not secure.")));
+ } else if (u_sess->attr.attr_security.Password_encryption_type == 1) { // sha256+MD5
+ datum_value = calculate_encrypted_combined_password(password, rolname, salt_string);
+ } else if (u_sess->attr.attr_security.Password_encryption_type == PASSWORD_TYPE_SM3) { //SM3加密
+ datum_value = gs_calculate_encrypted_sm3_password(password, salt_string);
+ } else { // sha256
+ datum_value = calculate_encrypted_sha256_password(password, rolname, salt_string);
+ }
+
+ return datum_value;
+}
+```
+
+
+
+
+
+##### MD5加密方式
+
+对于MD5加密算法,本文便不详细介绍
+
+> 可参考优质博客[ MD5 加密算法详解_红月修罗的博客-CSDN博客_md5是什么算法](https://blog.csdn.net/hawinlolo/article/details/94464237)
+
+MD5 算法将输入的信息进行分组,每组512 位(64个 字节),顺序处理完所有分组后输出128 位结果。
+在每一组消息的处理中,都要进行4 轮、每轮16 步、总计64 步的处理。其中每步计算中含一次左循环移位,每一步结束时将计算结果进行一次右循环移位。
+
+执行函数:**pg_md5_encrypt( )**
+
+```cpp
+/* common/backend/libpq/md5.cpp:308-349 */
+bool pg_md5_encrypt(const char* passwd, const char* salt, size_t salt_len, char* buf)
+{
+ ...
+}
+```
+
+
+
+##### MD5加密和SHA256加密方式
+
+**执行函数:calculate_encrypted_combined_password( )**
+
+```cpp
+/* gausskernel/optimizer/commands/user.cpp:5904-5958 */
+Datum calculate_encrypted_combined_password(const char* password, const char* rolname, const char* salt_string)
+{
+ ...
+}
+```
+
+
+
+##### SHA256加密方式
+
+执行函数:**calculate_encrypted_sha256_password( )**
+
+```cpp
+/* gausskernel/optimizer/commands/user.cpp:5960-6000 */
+Datum calculate_encrypted_sha256_password(const char* password, const char* rolname, const char* salt_string)
+{
+ ...
+}
+```
+
+
+
+##### SM3加密方式
+
+执行函数:**gs_calculate_encrypted_sm3_password( )**
+
+```cpp
+/* gausskernel/optimizer/commands/user.cpp:6002-6042 */
+static Datum gs_calculate_encrypted_sm3_password(const char* password, const char* salt_string)
+{
+ ...
+}
+```
+
+
+
+前面三种加密方式已经耳熟能详,互联网上也有了不少优质文章介绍。也正是因为这些加密算法大家耳熟能详,关注度高,这些**算法也逐渐被攻破**。为了保障信息安全,国家密码管理局于2010年制定并公布了一系列国产密码算法,简称国密算法。
+
+而openGauss也基于这一考量,引入了国密SM3加密算法,但由于引入的时间较短,相关文章也是寥寥无几。本着前人栽树后人乘凉的互联网理念,在这里详细介绍一下国密SM3。
+
+***
+
+#### openGauss引入国密SM3加密算法
+
+**SM3密码杂凑算法**是中国国家密码管理局2010年公布的中国商用密码杂凑算法标准。该算法于2012年发布为密码行业标准(GM/T 0004-2012),2016年发布为国家密码杂凑算法标准(GB/T 32905-2016)。
+
+SM3适用于商用密码应用中的数字签名和验证,是在SHA-256基础上改进实现的一种算法,其安全性和SHA-256相当。SM3和MD5的迭代过程类似,也采用Merkle-Damgard结构。消息分组长度为512位,摘要值长度为256位。
+
+整个算法的执行过程可以概括成四个步骤:**消息填充、消息扩展、迭代压缩、输出结果。**
+
+
+
+##### 1. 消息填充
+
+SM3的消息扩展是以512位的数据分组作为输入。
+
+对数据进行填充,填充方法同MD5的加密方式,填入“1”后,填入k个“0”,使得拓展长度满足
+
+```c++
+( n + 1 + k ) mod 512 = 448
+```
+
+那么为什么是448呢?因为后面要再填充一段64位长的空间存储数据的长度。
+
+如图所示:
+
+
+
+
+
+##### 2. 消息扩展
+
+将512位数据划分为16个消息字,利用第一个512位数据生成的16个消息字递推生成剩余的116个消息字
+
+总共迭代产生132个消息字,将前68个消息字记为`Wj`,后64个消息字记为`Wj'`。
+
+生成消息字伪代码如下
+
+```cpp
+def CF():
+for j = 16 to 67
+ Wj = P1(W_[j-16] xor W_[j-9] xor (W_[j-3] <<< 15 ) ) xor (W_[j-13] <<< 7) xor W_[j-6]
+
+for j = 0 to 63
+ Wj' = Wj xor W_[j+4]
+```
+
+如下图所展示的过程递归加密
+
+
+
+
+
+##### 3. 迭代压缩
+
+然后是分析具体的压缩函数:
+
+`前16个消息值`与`初值IV`作为输入进入压缩函数,得到的值`result1`作为下一次的输入和下一组`16个消息字`
+
+得到`result2`,依次递归得到最终的杂凑值。
+
+
+
+压缩函数的细节如下伪代码:
+
+```cpp
+ABCDEFGH = V_i
+V_(i+1) = CF(V(i), B(i))
+FOR j = 0 TO 63
+ SS1 = ((A <<< 12) + E + (T_j <<< j)) <<< 7
+ SS2 = SS1 xor (A <<< 12)
+ TT1 = FFj(A,B,C) + D + SS2 + Wj'
+ TT2 = GGj(E,F,G) + H + SS1 + Wj
+ D = C
+ C = B <<< 9
+ B = A
+ A = TT1
+ H = G
+ G = F <<< 19
+ F = E
+ E = P0(TT2)
+```
+
+压缩函数的流程图如图所示:
+
+
+
+
+
+##### 4 .输出结果
+
+最后由压缩函数的输出A、B、C、D、E、F、G、H八个变量拼接就可以得到最后的加密值啦!
+
+
+
+##### 5. 结合代码分析
+
+在已经了解了国密SM3的加密算法原理之后,让我们结合openGauss的源码看看是如何实现算法细节的。
+
+我们追踪到之前提到的sm3加密函数**gs_calculate_encrypted_sm3_password( )**
+
+```cpp
+/*
+ * INPUT: 口令password,盐值salt_string
+ * OUTPUT: 口令密文datum_value
+*/
+
+static Datum gs_calculate_encrypted_sm3_password(const char* password, const char* salt_string)
+{
+ char encrypted_sm3_password[SM3_PASSWD_LEN + 1] = {0}; // length: 196
+ char encrypted_sm3_password_complex[SM3_PASSWD_LEN + ITERATION_STRING_LEN + 1] = {0}; // length: 206
+ // iteration_string_len 迭代字符串长度
+ char iteration_string[ITERATION_STRING_LEN + 1] = {0}; // length: 12
+ Datum datum_value;
+ errno_t rc = EOK;
+
+ // 将各类参数传入核心加密函数GsSm3Encrypt中
+ if (!GsSm3Encrypt(password,
+ salt_string,
+ strlen(salt_string),
+ encrypted_sm3_password,
+ NULL,
+ u_sess->attr.attr_security.auth_iteration_count)) {
+ rc = memset_s(encrypted_sm3_password, SM3_PASSWD_LEN + 1, 0, SM3_PASSWD_LEN + 1);
+ securec_check(rc, "\0", "\0");
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("password encryption failed")));
+ }
+
+ ...
+
+ return datum_value;
+}
+```
+
+
+
+这里追踪一下常数,做出一些简单的计算已写入注释中:
+
+```CPP
+SM3_PASSWD_LEN = (ENCRYPTED_STRING_LENGTH + SM3_LENGTH) = 195
+
+ SM3_LENGTH = 3
+ ENCRYPTED_STRING_LENGTH = (HMAC_STRING_LENGTH + STORED_KEY_STRING_LENGTH + SALT_STRING_LENGTH) = 192
+ HMAC_STRING_LENGTH = (HMAC_LENGTH * 2) = 64
+ HMAC_LENGTH = 32
+ STORED_KEY_STRING_LENGTH = (STORED_KEY_LENGTH * 2) = 64
+ STORED_KEY_LENGTH = 32
+ SALT_STRING_LENGTH = (SALT_LENGTH * 2) = 64
+ SALT_LENGTH = 32
+
+
+ITERATION_STRING_LEN = 11
+```
+
+
+
+继续追踪加密核心函数**GsSm3Encrypt()**:
+
+```cpp
+/*
+ * INPUT: 口令password,盐值salt_s,盐值长度salt_len,密文缓冲区buf,
+ * 客户端密钥client_key_buf,迭代长度iteration_count
+ * OUTPUT: bool
+*/
+
+bool GsSm3Encrypt(
+ const char* password, const char* salt_s, size_t salt_len, char* buf, char* client_key_buf, int iteration_count)
+{
+ size_t password_len = 0;
+ char k[K_LENGTH + 1] = {0}; // K_LENGTH = 32
+ char client_key[CLIENT_KEY_BYTES_LENGTH + 1] = {0}; // CLIENT_KEY_BYTES_LENGTH = 32
+ char sever_key[HMAC_LENGTH + 1] = {0}; // HMAC_LENGTH = 32
+ char stored_key[STORED_KEY_LENGTH + 1] = {0}; // STORED_KEY_LENGTH = 32
+ char salt[SALT_LENGTH + 1] = {0}; // SALT_LENGTH = 32
+ char serverKeyString[HMAC_LENGTH * ENCRY_LENGTH_DOUBLE + 1] = {0}; // HMAC_LENGTH = 32
+ // ENCRY_LENGTH_DOUBLE = 2
+ char stored_key_string[STORED_KEY_LENGTH * 2 + 1] = {0}; // STORED_KEY_LENGTH = 32
+ int pkcs_ret;
+ int sever_ret;
+ int client_ret;
+ int hash_ret;
+ int hmac_length = HMAC_LENGTH;
+ int stored_key_length = STORED_KEY_LENGTH;
+ int total_encrypt_length;
+ char server_string[SEVER_STRING_LENGTH_SM3] = "Server Key";
+ char client_string[CLIENT_STRING_LENGTH] = "Client Key";
+ errno_t rc = 0;
+
+ if (NULL == password || NULL == buf) {
+ return false;
+ }
+
+ password_len = strlen(password);
+ /* Tranform string(64Bytes) to binary(32Bytes) */
+ /* 转换一下格式 */
+ sha_hex_to_bytes32(salt, (char*)salt_s);
+ /* calculate k */
+ /* 这里我们之前介绍过,满足(n+1+k) mod 512 = 448 */
+ // 生成消息认证码
+ pkcs_ret = PKCS5_PBKDF2_HMAC((char*)password,
+ password_len,
+ (unsigned char*)salt,
+ SALT_LENGTH,
+ iteration_count,
+ (EVP_MD*)EVP_sha1(),
+ K_LENGTH,
+ (unsigned char*)k);
+ if (!pkcs_ret) {
+ rc = memset_s(k, K_LENGTH + 1, 0, K_LENGTH + 1);
+ SECUREC_CHECK(rc);
+
+ return false;
+ }
+
+ /* We have already get k ,then we calculate client key and server key,
+ * then calculate stored key by using client key */
+
+ /* calculate server rkey */
+ /* 计算服务端密钥 */
+ sever_ret = CRYPT_hmac(NID_hmacWithSHA256,
+ (GS_UCHAR*)k,
+ K_LENGTH,
+ (GS_UCHAR*)server_string,
+ SEVER_STRING_LENGTH_SM3 - 1,
+ (GS_UCHAR*)sever_key,
+ (GS_UINT32*)&hmac_length);
+ if (sever_ret) {
+ rc = memset_s(k, K_LENGTH + 1, 0, K_LENGTH + 1);
+ SECUREC_CHECK(rc);
+
+ rc = memset_s(sever_key, HMAC_LENGTH + 1, 0, HMAC_LENGTH + 1);
+ SECUREC_CHECK(rc);
+ return false;
+ }
+
+ /* calculate client key */
+ /* 计算客户端密钥 */
+ client_ret = CRYPT_hmac(NID_hmacWithSHA256,
+ (GS_UCHAR*)k,
+ K_LENGTH,
+ (GS_UCHAR*)client_string,
+ CLIENT_STRING_LENGTH - 1,
+ (GS_UCHAR*)client_key,
+ (GS_UINT32*)&hmac_length);
+ if (client_ret) {
+ rc = memset_s(k, K_LENGTH + 1, 0, K_LENGTH + 1);
+ SECUREC_CHECK(rc);
+
+ rc = memset_s(client_key, CLIENT_KEY_BYTES_LENGTH + 1, 0, CLIENT_KEY_BYTES_LENGTH + 1);
+ SECUREC_CHECK(rc);
+
+ rc = memset_s(sever_key, HMAC_LENGTH + 1, 0, HMAC_LENGTH + 1);
+ SECUREC_CHECK(rc);
+
+ return false;
+ }
+
+ /* 转换回64位 */
+ if (NULL != client_key_buf) {
+ sha_bytes_to_hex64((uint8*)client_key, client_key_buf);
+ }
+
+ hash_ret = EVP_Digest(
+ (GS_UCHAR*)client_key, HMAC_LENGTH, (GS_UCHAR*)stored_key, (GS_UINT32*)&stored_key_length, EVP_sm3(), NULL);
+
+ if (!hash_ret) {
+ ...
+ // 安全检查
+ return false;
+ }
+
+ /* Mark the type in the stored string */
+ ...
+
+ /* We must clear the mem before we free it for the safe */
+ /* 清理内存,以确保安全*/
+ ...
+
+ return true;
+}
+
+```
+
+
+
+可惜只是看到了加密的整体流程,没有追踪到算法的具体细节。
+
+但根据注释
+
+```cpp
+ /* calculate server rkey */
+ sever_ret = CRYPT_hmac()
+
+GS_UINT32 CRYPT_hmac(GS_UINT32 ulAlgType, const GS_UCHAR* pucKey, GS_UINT32 upucKeyLen, const GS_UCHAR* pucData,
+ GS_UINT32 ulDataLen, GS_UCHAR* pucDigest, GS_UINT32* pulDigestLen)
+{
+ const EVP_MD* evp_md = get_evp_md_by_id(ulAlgType);
+ if (evp_md == NULL) {
+ return 1;
+ }
+#ifndef WIN32
+ if (!HMAC(evp_md, pucKey, (int)upucKeyLen, pucData, ulDataLen, pucDigest, pulDigestLen)) {
+ return 1;
+ }
+#else
+ if (!HMAC(evp_md, pucKey, (int)upucKeyLen, pucData, ulDataLen, pucDigest, (unsigned int*)pulDigestLen)) {
+ return 1;
+ }
+#endif
+ return 0;
+}
+```
+
+大致可以猜测到函数HMAC就是加密的关键。而且自此,便无法继续追踪下去,通过查阅资料
+
+[HMAC(Hash-based Message Authentication Code)实现原理 - yvivid - 博客园 (cnblogs.com)](https://www.cnblogs.com/yvivid/p/hmac_basic.html),可以确认,加密细节都在其中。
+
+***
+
+#### 总结
+
+本文重点分析了openGauss安全机制中的口令存储部分。安全形势总是日新月异,只有不断进步、创新,才能稳稳的保护用户的数据安全。正如**openGauss2022年主要规划特性:性能,可靠,安全所描述的样子**,采用新的加密算法国密SM3是一个不错的选择,也希望openGauss可以一步一步做下去,为更多用户提供优质的开源数据库软件。
+
+
+
+#### 补充
+
+openGauss用户口令强度校验机制:
+
+- 包含大写字母(A-Z)的最少个数(password_min_uppercase)
+- 包含小写字母(a-z)的最少个数(password_min_lowercase)
+- 包含数字(0-9)的最少个数(password_min_digital)
+- 包含特殊字符的最少个数(password_min_special)
+- 密码的最小长度(password_min_length)
+- 密码的最大长度(password_max_length)
+- 至少包含上述四类字符中的三类。
+- 不能和用户名、用户名倒写相同,本要求为非大小写敏感。
+- 不能和当前密码、当前密码的倒写相同。
+- 不能是弱口令。
+
+参数password_policy设置为1时表示采用密码复杂度校验,默认值为1。[点我返回](#口令加密过程)
+
+
diff --git "a/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\256\241\350\256\241\344\270\216\350\277\275\350\270\252.md" "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\256\241\350\256\241\344\270\216\350\277\275\350\270\252.md"
new file mode 100644
index 0000000000000000000000000000000000000000..c67f9507f070430a5194e83e6be94ca18f51875a
--- /dev/null
+++ "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\345\256\241\350\256\241\344\270\216\350\277\275\350\270\252.md"
@@ -0,0 +1,391 @@
+## openGauss安全机制之审计与追踪
+
+[TOC]
+
+### 前言
+
+本文《openGauss安全机制之审计与追踪》为第五届中国软件开源创新大赛——开源代码评注赛道参赛作品
+
+作者:袁建硕
+
+所属战队:共同学习
+
+时间:2022-10-05
+
+评注代码:[opengauss-mirror/openGauss-server: openGauss kernel (github.com)](https://github.com/opengauss-mirror/openGauss-server)
+
+***
+
+### 审计与追踪概述
+
+随着信息泄露事件的频发,数据库安全产品逐渐进入大众视野。而**数据库审计(DBAudit)**作为数据库安全技术之一,以安全事件为中心,以全面审计和精确审计为基础,实时记录网络上的数据库活动,对数据库操作进行细粒度审计的合规性管理,对数据库遭受到的风险行为进行实时告警。为用户带来诸多价值:
+
++ 满足合规要求,加强监管能力
++ 数据库为核心,操作全方位掌控
++ 行为准确定位,便于追查定责
++ 攻击精准识别,保证数据库系统安全
+
+数据库的审计一定程度上起到威慑作用,将审计作为证据,依靠法律武器积极维权,也是保护数据安全的重要措施之一。
+
+
+
+### 审计日志设计
+
+首先,我们先了解审计日志的结构设计,审计日志通常是存储在数据库的表中或者系统文件中,openGauss在设计上选择了后者,更好的利用了操作系统的文件权限管理。
+
+审计日志文件受操作系统权限的保护,默认只有初始化用户可以读写,保证了审计结果的安全性,避免被攻击篡改日志。
+
+由源码以及命名规则不难得知,每条审计记录对应一个**AuditData**结构体
+
+```cpp
+typedef struct AuditData {
+ AuditMsgHdr header; /* 记录文件头,存储记录的表示、大小等 */
+ AuditType type; /* 审计类型 */
+ AuditResult result; /* 执行结果 */
+ char varstr[1]; /* 二进制格式存储的具体审计信息 */
+} AuditData;
+```
+
+然后我们展开分析其中的数据结构
+
+**AuditMsgHdr、AuditType、AuditResult**
+
+#### AuditMsgHdr
+
+```cpp
+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;
+```
+
+
+
+#### AuditType
+
+都是一些常数,体现在日志的type字段
+
+```cpp
+typedef enum {
+ AUDIT_UNKNOWN_TYPE = 0,
+ AUDIT_LOGIN_SUCCESS,
+ AUDIT_LOGIN_FAILED,
+ AUDIT_USER_LOGOUT,
+ AUDIT_SYSTEM_START,
+ AUDIT_SYSTEM_STOP,
+ ...
+} AuditType;
+```
+
+
+
+#### AuditResult
+
+体现在result字段中
+
+```cpp
+typedef enum { AUDIT_UNKNOWN = 0, AUDIT_OK, AUDIT_FAILED } AuditResult;
+```
+
+
+
+#### 示例中的其他问题
+
+再回过头来看**审计日志单条信息示例**
+
+```sql
+time | 时间
+type | 类型
+result | 结果
+userid | 用户id
+username | 用户名
+database | 数据库名
+client_connifo | 客户端连接信息
+object_name | 目标名
+detail_info | 目标详细信息
+node_name | 结点名
+thread_id | 线程号
+local_port | 本地端口号
+remote_port | 远程端口号
+```
+
+
+
+那么除了前面的三个字段,**其他字段**在哪呢?
+
+我们在pgaudit.cpp文件找到这一段函数
+
+```cpp
+static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS],
+ AuditData *adata,
+ const AuditMsgHdr &header)
+{
+ /* append timestamp info to data tuple */
+ int i = 0;
+ values[i++] = TimestampTzGetDatum(time_t_to_timestamptz(adata->header.time));
+ values[i++] = CStringGetTextDatum(AuditTypeDesc(adata->type));
+ values[i++] = CStringGetTextDatum(AuditResultDesc(adata->result));
+
+ /*
+ * new format of the audit file under correct record
+ * the older audit file do not have userid info, so let it to be null
+ */
+ int index_field = 0;
+ const char* field = NULL;
+ bool new_version = (header.fields == PGAUDIT_QUERY_COLS);
+ field = new_version ? pgaudit_string_field(adata, index_field++) : NULL;
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* user id */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* user name */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* dbname */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* client info */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* object name */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* detail info */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* node name */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* thread id */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* local port */
+ field = pgaudit_string_field(adata, index_field++);
+ values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* remote port */
+
+ Assert(i == PGAUDIT_QUERY_COLS);
+}
+```
+
+可以看到,后面的所有字段都是在这里被加入的
+
+
+
+#### 日志组的设计
+
+我们已经了解单条审计信息的字段格式
+
+那么数据库操作中大量的日志又是**如何进行索引**的呢?
+
+Answer:这里采用了指针的结构。
+
+首先有**索引表**
+
+> [ 索引表 ]
+>
+> 索引表头|| 索引元素1 | 索引元素2 | 索引元素3 | ......
+
+
+
+然后由**每个索引元素**链接到**对应的审计文件表**中
+
+> [ 审计文件表 ]
+>
+> 审计记录1 | 审计记录2 | 审计记录3 | ......
+
+
+
+最后每个审计文件表中的结构就和之前示例中的相一致啦
+
+> [ 审计记录 ]
+>
+> 审计记录头 | 审计类行 | 审计结果 | 具体审计数据
+
+如图所示:
+
+
+
+然后让我们联系代码去看一看
+
+
+
+#### 联系代码
+
+首先看**索引表的结构体**
+
+```cpp
+typedef struct AuditIndexTable {
+ uint32 maxnum; /* 表中可存储的审计文件的最大数量 */
+ uint32 begidx; /* 首个审计文件的位置 */
+ uint32 curidx; /* 当前操作的审计文件的位置 */
+ uint32 count; /* 当前表中存储的审计文件的数量 */
+ pg_time_t last_audit_time; /* 最后一次写入审计记录的时间 */
+ AuditIndexItem data[1]; /* 审计文件指针 */
+} AuditIndexTable;
+```
+
+
+
+
+
+然后我们继续追踪**审计文件的结构体**AuditIndexItem
+
+```cpp
+typedef struct AuditIndexItem {
+ pg_time_t ctime; /* 审计文件创建时间 */
+ uint32 filenum; /* 审计文件的名 */
+ uint32 filesize; /* 审计文件的大小 */
+} AuditIndexItem;
+```
+
+
+
+这里以审计记录的写入函数audit_report为例
+
+```cpp
+/*
+ * Brief : 向系统审计员报告审计信息
+ * Description : 由后端调用,通常以一下过程进行
+ * 1. 验证类型和进程,决定是否去报告审计
+ * 2. 从连接中得到所有审计信息
+ * 3. 添加审计信息到字符缓冲区中
+ * 4. 最后,写入审计文件或者发送给审计员进程去处理
+ */
+
+void audit_report(AuditType type, AuditResult result, const char *object_name, const char *detail_info,
+ AuditClassType ctype)
+{
+ /* 检查进程状态 */
+ if (!audit_status_check_ok() || (detail_info == NULL)) {
+ return;
+ }
+
+ /* 检查审计类型 */
+ if (!audit_type_validcheck(type)) {
+ return;
+ }
+
+ /* 从端口中得到信息 */
+ StringInfoData buf;
+ AuditData adata;
+ AuditEventInfo event_info;
+ if (!audit_get_clientinfo(type, object_name, event_info)) {
+ return;
+ }
+ char *userid = event_info.userid; // 用户id
+ const char* username = event_info.username; // 用户名
+ const char* dbname = event_info.dbname; // 数据库名
+ char* client_info = event_info.client_info; // 客户端信息
+ char* threadid = event_info.threadid; // 进程号
+ char* localport = event_info.localport; // 本地端口
+ char* remoteport = event_info.remoteport; // 远程端口
+
+ /* append xid info when audit_xid_info = 1 */
+ char *detail_info_xid = NULL;
+ bool audit_xid_info = (u_sess->attr.attr_security.audit_xid_info == 1);
+ if (audit_xid_info) {
+ uint32 len = uint64_max_len + strlen("xid=, ") + strlen(detail_info) + 1;
+ detail_info_xid = (char *)palloc0(len);
+ audit_append_xid_info(detail_info, detail_info_xid, len);
+ }
+
+ /* 数据头赋值 */
+ adata.header.signature[0] = 'A';
+ adata.header.signature[1] = 'U';
+ adata.header.version = 0;
+ adata.header.fields = PGAUDIT_QUERY_COLS;
+ adata.header.flags = AUDIT_TUPLE_NORMAL;
+ adata.header.time = current_timestamp();
+ adata.header.size = 0;
+ adata.type = type;
+ adata.result = result;
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf, (char*)&adata, AUDIT_HEADER_SIZE);
+
+ /* 审计数据赋值 */
+ appendStringField(&buf, userid);
+ appendStringField(&buf, username);
+ appendStringField(&buf, dbname);
+ appendStringField(&buf, (client_info[0] != '\0') ? client_info : NULL);
+ appendStringField(&buf, object_name);
+ appendStringField(&buf, (!audit_xid_info) ? detail_info : detail_info_xid);
+ appendStringField(&buf, g_instance.attr.attr_common.PGXCNodeName);
+ appendStringField(&buf, (threadid[0] != '\0') ? threadid : NULL);
+ appendStringField(&buf, (localport[0] != '\0') ? localport : NULL);
+ appendStringField(&buf, (remoteport[0] != '\0') ? remoteport : NULL);
+
+ /*
+ * Use the chunking protocol if we know the syslogger should be
+ * catching stderr output, and we are not ourselves the syslogger.
+ * Otherwise, just do a vanilla write to stderr.
+ */
+ if (WRITE_TO_AUDITPIPE) {
+ write_pipe_chunks(buf.data, buf.len, ctype);
+ } else if (WRITE_TO_STDAUDITFILE(ctype)) {
+ pgaudit_write_file(buf.data, buf.len);
+ } else if (WRITE_TO_UNIAUDITFILE(ctype)) {
+ pgaudit_write_policy_audit_file(buf.data, buf.len);
+ } else if (detail_info != NULL) {
+ ereport(LOG, (errmsg("discard audit data: %s", (!audit_xid_info) ? detail_info : detail_info_xid)));
+ }
+
+ if (detail_info_xid != NULL) {
+ pfree(detail_info_xid);
+ }
+ pfree(buf.data);
+}
+```
+
+
+
+### 审计执行的原理
+
+openGauss提供对用户发起的SQL行为审计和追踪能力,支持对DDL、DML语句和关键行为的审计。
+
+> 简单介绍一下DDL语句与DML语句:
+>
+> DML(Data Manipulation Language)数据操纵语言:
+>
+> 适用范围:对数据库中的数据进行一些简单操作,如insert,delete,update,select等.
+>
+>
+>
+> DDL(Data Definition Language)数据定义语言:
+>
+> 适用范围:对数据库中的某些对象(例如,database,table)进行管理,如Create,Alter和Drop.
+>
+
+工作线程初始化时便加载了审计模块,审计的执行原理是将审计函数赋给SQL生命周期不同阶段的HOOK函数。
+
+同样在阅读代码之后,定位到了加载审计模块的关键函数**pgaudit_agent_init( )**
+
+```cpp
+/*
+ * Brief : perfstat_agent_init()
+ * Description : Module load callback.
+ * Notes : Called from postmaster.
+ */
+void pgaudit_agent_init(void)
+{
+ if (!IsPostmasterEnvironment || !u_sess->attr.attr_security.Audit_enabled ||
+ u_sess->exec_cxt.g_pgaudit_agent_attached) {
+ return;
+ }
+ prev_ExecutorEnd = ExecutorEnd_hook;
+ ExecutorEnd_hook = pgaudit_ExecutorEnd;
+ set_pgaudit_prehook(ProcessUtility_hook);
+ ProcessUtility_hook = (ProcessUtility_hook_type)pgaudit_ProcessUtility;
+ u_sess->exec_cxt.g_pgaudit_agent_attached = true;
+}
+```
+
+SQL语句在指向到ProcessUtility_hook和ExecutorEnd_hook 函数指针时,会分别进入预置好的审计流程中。
+
+这两个函数指针的位置在SQL进入执行器执行之前,具体关系如图:
+
+
+
+在计划期到执行器的途中,生成了计划树,此时审计模块则调用pgaudit_ExecutorEnd和pgaudit_ProcessUtility函数分别对DML语句和DDL语句进行分析。
+
+***
+
+### 总结
+
+本文概述了openGauss安全机制中的审计与追踪,主要包括审计日志的设计以及审计执行的原理。
+
+通过审计的的确确有效地维护了数据库的安全。但对于审计文件而已,过于依赖了操作系统对文件的保护,是openGuass仍然可以改进的地方。
diff --git "a/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272\343\200\201\347\256\241\347\220\206\344\270\216\350\256\244\350\257\201.md" "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272\343\200\201\347\256\241\347\220\206\344\270\216\350\256\244\350\257\201.md"
new file mode 100644
index 0000000000000000000000000000000000000000..c76c0d584f420f560f3b42b5638828f51a752e6c
--- /dev/null
+++ "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\346\234\272\345\210\266\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272\343\200\201\347\256\241\347\220\206\344\270\216\350\256\244\350\257\201.md"
@@ -0,0 +1,1082 @@
+## openGauss安全机制之角色创建、管理与认证
+
+[TOC]
+
+### 前言
+
+本文《openGauss安全机制之角色创建、管理与认证》为第五届中国软件开源创新大赛——开源代码评注赛道参赛作品
+
+作者:袁建硕
+
+所属战队:共同学习
+
+时间:2022-09-28
+
+评注代码:[opengauss-mirror/openGauss-server: openGauss kernel (github.com)](https://github.com/opengauss-mirror/openGauss-server)
+
+***
+
+### 角色创建与角色管理概述
+
+数据库中,用户要想访问具体的数据库对象,需要各类复杂的权限,而如果采用逐个权限授予用户的方式,则会非常麻烦并且容易出错,不利于权限的管理。因此openGauss采用基于角色进行权限的管理,这种方式则能够实现权限的高效管理。
+
+>什么是角色?
+>
+>角色是一组权限的集合,可以被赋予指定的权限并分配给用户,拥有角色的用户也就拥有了角色对应的权限。(注意:角色没有登陆权限,无法通过角色登录数据库)
+
+在数据库中,各种各样的角色拥有不同的权限,不同的操作,当对角色进行权限的授予和撤销时将会影响拥有该角色的所有用户。因此,对角色的区分管理及安全管控十分重要。
+
+***
+
+### 角色创建
+
+角色是拥有数据库对象和权限的实体,在不同的环境中角色可以是一个用户、一个组或者两者均有。
+
+在openGuass上创建一个角色,可以使用SQL命令CREATE ROLE
+
+```sql
+CREATE ROLE role_name [ [ WITH ] option [ ... ] ] [ ENCRYPTED | UNENCRYPTED ] { PASSWORD | IDENTIFIED BY } { 'password' | DISABLE};
+```
+
+
+
+创建角色调用函数CreateRole实现
+
+```cpp
+/* gausskernel/optimizer/commands/user.cpp: 548-1555 */
+void CreateRole(CreateRoleStmt* stmt)
+{
+ ...
+}
+```
+
+
+
+其传入参数的结构体为:
+
+```cpp
+typedef struct CreateRoleStmt {
+ NodeTag type;
+ RoleStmtType stmt_type; /* ROLE/USER/GROUP 创建角色的类型 角色/用户/组用户 */
+ char* role; /* role name 角色名称 */
+ List* options; /* List of DefElem nodes 角色属性列表,为一个链表结构 */
+} CreateRoleStmt;
+```
+
+
+
+然后查看函数声明中出现频率最高的结构体DefElem
+
+```cpp
+typedef struct DefElem {
+ NodeTag type;
+ char *defnamespace; /* 节点对应的命名空间 */
+ char *defname; /* 节点对应的角色属性名 */
+ Node *arg; /* 表示值或类型名 */
+ DefElemAction defaction; /* SET/ADD/DROP等其他未指定的行为 */
+ int begin_location; /* token begin location, or -1 if unknown */
+ int end_location; /* token end location, or -1 if unknown */
+ int location;
+} DefElem;
+```
+
+
+
+#### 创建流程
+
+流程如图所示:
+
+
+
+
+##### 1.判断创建的角色类型
+
+仍然是函数**CreateRole**函数中
+
+```cpp
+switch (stmt->stmt_type) {
+ case ROLESTMT_ROLE:
+ break;
+ case ROLESTMT_USER:
+ canlogin = true;
+ break;
+ case ROLESTMT_GROUP:
+ break;
+ default:
+ break;
+ }
+```
+
+当所创建的角色类型为用户时,将canlogin设置为true,这是因为用户默认具有登录权限。
+
+
+
+##### 2. 循环获取角色属性options
+
+CreateRole: 657-894
+
+从源码中可以看到
+
+大量使用了strcmp判断各类属性的是否存在以及其value
+
+包括password口令、encryptedPassword加密口令、sysid系统号等字段
+
+```cpp
+/* 从Node tree中获取option */
+foreach (option, stmt->options)
+{
+ ...
+}
+```
+
+
+
+##### 3.将获取的属性转换成对应的数据类型
+
+CreateRole: 896-1166
+
+采用了大量的if判断结构将对应的参数信息转换为需要的角色属性值类型
+
+```cpp
+...
+ if (dinherit != NULL)
+ inherit = intVal(dinherit->arg) != 0;
+ if (dcreaterole != NULL)
+ createrole = intVal(dcreaterole->arg) != 0;
+ if (dcreatedb != NULL)
+ createdb = intVal(dcreatedb->arg) != 0;
+ if (duseft != NULL)
+ useft = intVal(duseft->arg) != 0;
+ if (dcanlogin != NULL)
+ canlogin = intVal(dcanlogin->arg) != 0;
+ if (disreplication != NULL)
+ isreplication = intVal(disreplication->arg) != 0;
+...
+```
+
+
+
+##### 4. 将要创建的角色属性值构建pg_authid元组
+
+CreateRole: 1168-1178
+
+```cpp
+/*
+ * 检查pg_authid是否已经存在
+ */
+ Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+ TupleDesc pg_authid_dsc = RelationGetDescr(pg_authid_rel);
+
+ if (OidIsValid(get_role_oid(stmt->role, true))) {
+ str_reset(password);
+ ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", stmt->role)));
+ }
+```
+
+之后就是检验时间戳是否超时以及对口令的再次检查
+
+CreateRole: 1233-1371
+
+创建一个插入的元组
+
+```cpp
+/*
+ * Build a tuple to insert
+ */
+ errno_t errorno = memset_s(new_record, sizeof(new_record), 0, sizeof(new_record));
+ securec_check(errorno, "\0", "\0");
+ errorno = memset_s(new_record_nulls, sizeof(new_record_nulls), false, sizeof(new_record_nulls));
+ securec_check(errorno, "\0", "\0");
+
+ new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
+
+ new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
+ new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
+ new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
+ new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
+ ...
+```
+
+
+
+##### 5. 将tuple写入系统表并更新
+
+CreateRole: 1372-1413
+
+```cpp
+ HeapTuple tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
+
+ if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid)) {
+ HeapTupleSetOid(tuple, u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid);
+ u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid = InvalidOid;
+ }
+
+ /*
+ * Insert new record in the pg_authid table
+ */
+ roleid = simple_heap_insert(pg_authid_rel, tuple);
+
+ /* add dependency of roleid on rpoid, no need add dependency on default_pool */
+ if (IsUnderPostmaster) {
+ if (OidIsValid(rpoid) && (rpoid != DEFAULT_POOL_OID))
+ recordDependencyOnRespool(AuthIdRelationId, roleid, rpoid);
+
+ u_sess->wlm_cxt->wlmcatalog_update_user = true;
+ }
+ ...
+```
+
+
+
+##### 6. 将新创建的角色加入指定存在的父角色中
+
+CreateRole: 1414-1430
+
+```cpp
+/*
+ * Add the new role to the specified existing roles.
+ */
+ foreach (item, addroleto) {
+ char* oldrolename = strVal(lfirst(item));
+ Oid oldroleid = get_role_oid(oldrolename, false);
+
+ AddRoleMems(
+ oldrolename, oldroleid, list_make1(makeString(stmt->role)), list_make1_oid(roleid), GetUserId(), false);
+ }
+
+ /*
+ * Add the specified members to this new role. adminmembers get the admin
+ * option, rolemembers don't.
+ */
+ AddRoleMems(stmt->role, roleid, adminmembers, roleNamesToIds(adminmembers), GetUserId(), true);
+ AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);
+```
+
+
+
+到此为止,完成了整个角色创建的过程
+
+
+
+### 角色管理
+
+角色管理主要包括以下内容:
+
++ 修改角色属性
++ 删除角色
++ 授予和回收角色
+
+
+
+#### 修改角色属性
+
+SQL语句 ALTER ROLE修改数据库角色
+
+通过调用AlterRole函数来实现角色属性的修改
+
+查看关键结构体AlterRoleStmt
+
+```cpp
+typedef struct AlterRoleStmt {
+ NodeTag type;
+ char* role; /* 角色名称 */
+ List* options; /* 需要修改的属性列表 */
+ int action; /* +1 = 增加成员关系, -1 = 删除成员关系 */
+ RoleLockType lockstatus; /* 角色锁定状态 */
+} AlterRoleStmt;
+```
+
+大致流程为:
+
+AlterRole → 循环提取要修改的属性options → 转换为对应的数据类型 → 判断角色是否存在 → 判断角色是否有修改的权限 → 更新后的属性值同步到新元组中 → 新元组替代旧元组 → 判断成员关系action → 关闭系统表 → 结束
+
+
+
+其结构与**角色创建**时大同小异
+
+这里主要分析后两次判断:
+
+- 判断角色是否有修改的权限
+- 判断成员关系action
+
+
+
+**判断角色是否有修改的权限:**
+
+AlterRole:2258-2357
+
+不同操作要求的权限不同,权限不足会报错提示
+
+```cpp
+/* Database Security: Support separation of privilege.*/
+ if (roleid == BOOTSTRAP_SUPERUSERID) {
+ if (!(issuper < 0 && inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 &&
+ isauditadmin < 0 && issystemadmin < 0 && ismonitoradmin < 0 && isoperatoradmin < 0 &&
+ ispolicyadmin < 0 && isvcadmin < 0 && useft < 0 && ispersistence < 0 && dconnlimit == NULL &&
+ rolemembers == NULL && validBegin == NULL && validUntil == NULL && drespool == NULL &&
+ dparent == NULL && dnode_group == NULL && dspacelimit == NULL && dtmpspacelimit == NULL &&
+ dspillspacelimit == NULL)) {
+ str_reset(password);
+ str_reset(replPasswd);
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("Permission denied to change privilege of the initial account.")));
+ }
+ }
+ ...
+```
+
+
+
+**判断成员关系action:**
+
+AlterRole:2999-3002
+
+```cpp
+ if (stmt->action == +1) /* add members to role */
+ AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false);
+ else if (stmt->action == -1) /* drop members from role */
+ DelRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), false);
+```
+
+
+
+
+
+#### 删除角色
+
+SQL命令:DROP ROLE
+
+通过函数DropRole实现,其对应结构体DropRoleStmt
+
+```cpp
+typedef struct DropRoleStmt {
+ NodeTag type;
+ List* roles; /* 要删除的角色列表 */
+ bool missing_ok; /* 判断角色是否存在 */
+ bool is_user; /* 删除的时角色还是用户 */
+ bool inherit_from_parent; /* 是否继承父角色 */
+ DropBehavior behavior; /* 是否级联删除依赖对象 */
+} DropRoleStmt;
+```
+
+删除的大致流程为:
+
+判断是否有删除角色的权限 → 检查要删除的角色是否存在 → 再次检查是否有权删除此角色 → 此角色是否有依赖对象 → 删除pg_authid中对应的元组,删除pg_auth_member中相关元组 → 关闭系统表 → 结束
+
+
+
+**注意两次删除权限的判断**,第一次是当前角色是否可以执行删除的操作,第二次是当前角色是否有权限删除目标角色
+
+从逻辑上讲,也可以先循环处理删除的角色,判断要删除角色是否存在,再依次进行两次判断,但这样操作的话明显增加了复杂度,属于是没有必要
+
+
+
+user.cpp : 3098-3383
+
+函数have_createrole_privilege()检查是否有权删除
+
+下面这段代码校验执行者和被删除角色的权限
+
+```cpp
+if ((((Form_pg_authid)GETSTRUCT(tuple))->rolsuper || ((Form_pg_authid)GETSTRUCT(tuple))->rolsystemadmin) &&
+ !isRelSuperuser())
+ ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));
+
+ Datum datum = heap_getattr(tuple, Anum_pg_authid_roloperatoradmin, pg_authid_dsc, &isNull);
+ if (!isNull) {
+ is_opradmin = DatumGetBool(datum);
+ } else if (roleid == BOOTSTRAP_SUPERUSERID) {
+ is_opradmin = true;
+ }
+ if (is_opradmin && !initialuser()) {
+ ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));
+ }
+
+ /* Forbid createrole holders to drop auditadmin when PrivilegesSeparate enabled. */
+ if ((((Form_pg_authid)GETSTRUCT(tuple))->rolauditadmin) &&
+ g_instance.attr.attr_security.enablePrivilegesSeparate && !isRelSuperuser())
+ ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied.")));
+
+```
+
+
+
+针对级联的情况,删除该角色拥有的对象
+
+```cpp
+ /*
+ * Drop the objects owned by the role if its behavior mod is CASCADE
+ */
+ if (stmt->behavior == DROP_CASCADE) {
+ char* user = NULL;
+
+ CancelQuery(role);
+ user = (char*)palloc(sizeof(char) * strlen(role) + 1);
+ errno_t errorno = strncpy_s(user, strlen(role) + 1, role, strlen(role));
+ securec_check(errorno, "\0", "\0");
+ drop_objectstmt.behavior = stmt->behavior;
+ drop_objectstmt.type = T_DropOwnedStmt;
+ drop_objectstmt.roles = list_make1(makeString(user));
+
+ DropOwnedObjects(&drop_objectstmt);
+ list_free_deep(drop_objectstmt.roles);
+ }
+```
+
+
+
+检查是否有对象依赖于该角色,如果有则提示报错
+
+```cpp
+/* Check for pg_shdepend entries depending on this role */
+ if (checkSharedDependencies(AuthIdRelationId, roleid, &detail, &detail_log))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("role \"%s\" cannot be dropped because some objects depend on it", role),
+ errdetail_internal("%s", detail),
+ errdetail_log("%s", detail_log)));
+```
+
+
+
+#### 授予和回收角色
+
+SQL命令:GRANT/REVOKE
+
+通过函数GrantRole来实现
+
+结构体GrantRoleStmt
+
+
+
+```cpp
+typedef struct GrantRoleStmt {
+ NodeTag type;
+ List* granted_roles; /* 被授予或回收的角色集合 */
+ List* grantee_roles; /* 从granted_roles中增加或删除的角色集合 */
+ bool is_grant; /* true = GRANT授权, false = REVOKE回收 */
+ bool admin_opt; /* 是否带有admin选项 */
+ char* grantor; /* 授权者 */
+ DropBehavior behavior; /* 是否级联回收角色 */
+} GrantRoleStmt;
+```
+
+大致流程:
+
+检查有权添加/删除角色成员 → 循环处理要添加/删除的角色 → 判断添加的成员是否已属于此角色 → 创建/修改pg_auth_member元组 → 新元组插入系统表 → 关闭系统表 → 结束
+
+对角色的操作都大差不差,这里不过多介绍
+
+
+
+
+
+### 身份认证简要逻辑
+
+在了解了角色创建以及角色管理的流程后,让我们继续分析openGuass是如何对身份进行认证的。
+
+openGauss的访问规则在配置文件HBA(Host-Based Authentication,主机认证)中。
+
+格式为:
+
+```sql
+hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
+```
+
+第一个字段代表套接字方法
+
+第二个字段代表被允许访问的数据库
+
+第三个字段代表允许访问的用户
+
+第四个字段代表允许访问的IP地址
+
+第五个字段代表访问的认证方式
+
+第六个字段对第五个字段认证信息的补充
+
+自左向右,访问需求规则的优先级逐渐降低
+
+源码如下:
+
+```c
+/* src/include/lib/hba.h: 39-66 */
+typedef struct HbaLine {
+ int linenumber; /* 规则行号 */
+ ConnType conntype; /* 连接套接字方法 */
+ List* databases; /* 允许访问的数据库集合 */
+ List* roles; /* 允许访问的用户组 */
+ ...
+ char* hostname; /* 允许访问的IP地址 */
+ UserAuth auth_method; /* 认证方法 */
+ ...
+} HbaLine;
+```
+
+HBA文件在系统管理员配置完成后存放在数据库服务侧。
+
+当用户通过数据库用户发起的认证请求时,信息被存放在数据结构Port中
+
+源码如下:
+
+```C
+/* src/include/libpq/libpq-be.h: 98-204 */
+ SockAddr laddr; /* 本地进程IP地址信息 */
+ SockAddr raddr; /* 远程客户端进程IP地址信息 */
+ char* remote_host; /* 远端主机名称字符串或IP地址 */
+ char* remote_hostname; /* (可选)远程主机名称字符串或IP地址 */
+
+ /* 发送给后端的数据包信息,包括访问的数据库名称、用户名、配置参数 */
+ 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(Generic Security Service, 通用安全服务)数据内容 */
+ gss_cred_id_t gss_cred; /* GSS 凭证信息 */
+ gss_name_t gss_name; /* GSS target name */
+ gss_buffer_desc gss_outbuf; /* GSS token信息 */
+#endif
+} Port;
+```
+
+
+
+在得到Port信息后,后台服务线程会根据前端传入的信息与**HbaLine**中记录的信息逐一比较,完成对应的身份识别。
+
+在**check_hba函数**中,可以看到身份认证的核心逻辑代码:
+
+```c
+/* src/common/backend/libpq/hba.cpp: 1523-1716 */
+/* */
+/* 扫描HBA文件,寻找匹配连接请求的规则项 */
+static void check_hba(hbaPort* port)
+{
+ ...
+ /* 获取目标用户的ID号 */
+ roleid = get_role_oid(port->user_name, true);
+ ...
+
+ /* */
+ foreach (line, g_instance.libpq_cxt.comm_parsed_hba_lines) {
+ errno_t rc = memcpy_s(hba, sizeof(HbaLine), lfirst(line), sizeof(HbaLine));
+ securec_check(rc, "\0", "\0");
+ /* 检查连接类型,考虑时本地连接行为还是远程连接行为 */
+ if (hba->conntype == ctLocal) {
+ /* 对于local套接字,仅允许初始安装用户本地登录 */
+ if (roleid == INITIAL_USER_ID) {
+ uid_t uid = 0;
+ gid_t gid = 0;
+ /* 得到当前系统用户名,基于本地uid */
+ if (getpeereid(port->sock, &uid, &gid) != 0) {
+ pfree_ext(hba);
+ ...
+ }
+
+ /*
+ * For connections from another coordinator, we could not
+ * get the userid. This case may also exist for other cases,
+ * like tools. (Usually happed when login with -h localhost
+ * or 127.0.0.1)
+ */
+ if ((long)uid == USER_NULL_MASK) {
+ continue;
+ }
+ /* 对照用户名,如果不匹配,设置为uaTrust不信任模式 */
+ isUsernameSame= IsSysUsernameSameToDB(uid, port->user_name);
+ if (!isUsernameSame && hba->auth_method == uaTrust) {
+ hba->auth_method = get_default_auth_method(port->user_name);
+ }
+ } else if (hba->auth_method == uaTrust || hba->auth_method == uaPeer) {
+ /* 访问用户与本地系统用户不相匹配的场景,需要提供密码 */
+ hba->auth_method = get_default_auth_method(port->user_name);
+ }
+
+ if (!IS_AF_UNIX(port->raddr.addr.ss_family))
+ continue;
+ } 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;
+
+ /* 记录相关信息以及继续执行校验 */
+ if (hba->conntype != ctLocal) {
+ /*
+ * 通常,带有信任模式的远程连接请求是被禁止的
+ * 但有一些例外场景:
+ * 1 由协调器和内部维护工具创建的连接请求
+ * -- 信任模式被允许,在process_startup_options函数中实现了对内部组的检查
+ * 2 本地环回连接
+ * -- 密码被增强
+ * 3 连接是由到协调器的组产生的非初始用户
+ * -- 密码被增强
+ */
+ if (hba->auth_method == uaTrust) {
+ if (IsConnPortFromCoord(port) || u_sess->proc_cxt.IsInnerMaintenanceTools) {
+ /* 第一种情况,直接跳过 */
+ } else if (IsLoopBackAddr(port)) {
+ /* 第二种情况, 本地环回连接, hba->remote_trust 设置为false */
+ hba->auth_method = get_default_auth_method(port->user_name);
+ hba->remoteTrust = false;
+ } else if (roleid != INITIAL_USER_ID && IS_PGXC_COORDINATOR && is_cluster_internal_connection(port)) {
+ /*
+ * 第三种情况
+ * 因为在成功认证之前,我们不能通过 pgxc_node 节点。
+ * 我们能在重新连接时得到一个过度保守的内部组判断
+ * 然而,这些情况不应该时问题,因为周期性地本地cm_agent连接很亏就会在共享内存中填充节点信息
+ */
+ hba->auth_method = get_default_auth_method(port->user_name);
+ hba->remoteTrust = false;
+ } else {
+ ConnAuthMethodCorrect = false;
+ pfree_ext(hba);
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("Forbid remote connection with trust method!")));
+ }
+ }
+ }
+
+#ifdef USE_IAM
+ /* 当使用带有SHA256/MD5/SM3加密时地iam用户去避免冲突时时,更改内部认证方式到IAM */
+ if (isIAM && (hba->auth_method == uaSHA256 || hba->auth_method == uaMD5 || hba->auth_method == uaSM3)) {
+ hba->auth_method = uaIAM;
+ }
+#endif
+#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+ /* 对于非初始化地用户, krb authentication是不被允许的, sha256 请求将会被通过. */
+ if (hba->auth_method == uaGSS && roleid != INITIAL_USER_ID && IsConnFromApp()) {
+ hba->auth_method = get_default_auth_method(port->user_name);
+ }
+#endif
+ isMatched = true;
+ break;
+ }
+
+ /* 无匹配规则,直接拒绝当前连接请求 */
+ if (!isMatched) {
+ hba->auth_method = uaImplicitReject;
+ }
+ copy_hba_line(hba);
+ port->hba = hba;
+ PG_END_ENSURE_ERROR_CLEANUP(hba_rwlock_cleanup, (Datum)0);
+ (void)pthread_rwlock_unlock(&hba_rwlock);
+}
+```
+
+
+
+
+
+### 认证机制核心流程
+
+在梳理了身份验证的基本逻辑之后,我们再去追溯一下认证机制的核心流程
+
+大致流程如图:
+
+
+
+
+#### 线程会话初始化入口
+
+**执行函数:InitSession( )**
+
+代码位置:
+
+src\gausskernel\process\threadpool\threadpool_worker.cpp: 778-892
+
+**传参:session**
+
+接受用户的身份信息session,切换至对应的空间地址mem
+
+```cpp
+AutoContextSwitch memSwitch(session->mcxt_group->GetMemCxtGroup(MEMORY_CONTEXT_DEFAULT));
+```
+
+
+
+更新工作版本Working version
+
+```cpp
+t_thrd.proc->workingVersionNum = pg_atomic_read_u32(&WorkingGrandVersionNum);
+```
+
+
+
+初始化GUC(Grand Unified Configuration),并读取相关内容
+
+```cpp
+InitializeGUCOptions();
+read_nondefault_variables();
+```
+
+
+
+安全地向用户端client发送错误信息
+
+```cpp
+t_thrd.postgres_cxt.whereToSendOutput = DestRemote;
+// 思考在781行为什么设置暂不发送?
+// t_thrd.postgres_cxt.whereToSendOutput = DestNone;
+// 这里猜测是输出的状态信息在验证session的过程中会通过注入等方式泄露高权限用户的信息
+```
+
+
+
+初始化端口号和连接
+
+```cpp
+if (!InitPort(session->proc_cxt.MyProcPort)) {
+ ...
+ }
+```
+
+
+
+再次切换工作版本号(从端口中得到的新版本号,why?)
+
+```cpp
+t_thrd.proc->workingVersionNum = session->proc_cxt.MyProcPort->SessionVersionNum;
+```
+
+
+
+增加进程定义模式
+
+```cpp
+ Reset_Pseudo_CurrentUserId();
+
+ SetProcessingMode(InitProcessing);
+
+ SessionSetBackendOptions();
+```
+
+
+
+允许SIGINT的系统调用去中止初始化程序
+
+```
+gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
+```
+
+
+
+初始化合法消息系统、状态、文件存储读取和流管理
+
+```cpp
+ SharedInvalBackendInit(false, true);
+ pgstat_initialize_session();
+ InitFileAccess();
+ smgrinit();
+```
+
+
+
+初始化openguass
+
+```cpp
+ char* dbname = session->proc_cxt.MyProcPort->database_name;
+ char* username = session->proc_cxt.MyProcPort->user_name;
+ t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid, username);
+ t_thrd.proc_cxt.PostInit->InitSession();
+```
+
+
+
+多节点情况下,执行操作
+
+```cpp
+#ifndef ENABLE_MULTIPLE_NODES
+ if (u_sess->proc_cxt.MyDatabaseId != InvalidOid && DB_IS_CMPT(B_FORMAT)) {
+ if (!u_sess->attr.attr_sql.dolphin) {
+ LoadDolphinIfNeeded();
+ } else {
+ InitBSqlPluginHookIfNeeded();
+ }
+ } else if (u_sess->proc_cxt.MyDatabaseId != InvalidOid && DB_IS_CMPT(A_FORMAT) && u_sess->attr.attr_sql.whale) {
+ InitASqlPluginHookIfNeeded();
+ }
+#endif
+```
+
+
+
+初始化哈希hash表
+
+```cpp
+if (IS_PGXC_COORDINATOR) {
+ init_set_params_htab();
+ }
+```
+
+
+
+检查存储
+
+```cpp
+if (t_thrd.utils_cxt.gs_mp_inited && processMemInChunks > maxChunksPerProcess) {
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_LOGICAL_MEMORY),
+ errmsg("memory usage reach the max_dynamic_memory"),
+ errdetail("current memory usage is: %u MB, max_dynamic_memory is: %u MB",
+ (unsigned int)processMemInChunks << (chunkSizeInBits - BITS_IN_MB),
+ (unsigned int)maxChunksPerProcess << (chunkSizeInBits - BITS_IN_MB))));
+ }
+
+ ReadyForQuery((CommandDest)t_thrd.postgres_cxt.whereToSendOutput);
+```
+
+
+
+#### 连接认证总入口
+
+**执行函数:PerformAuthentication( )**
+
+代码位置
+
+src\common\backend\utils\init\postinit.cpp: 287-344
+
+**传参:port**
+
+设置时效性,以防止意外bug
+
+```cpp
+if (!enable_sig_alarm(u_sess->attr.attr_security.AuthenticationTimeout * 1000, true))
+ ereport(FATAL, (errmsg("could not set timer for authorization timeout")));
+```
+
+
+
+解锁SIGUSR2权限使触发SIGALARM去处理超时
+
+```cpp
+old_sigset = gs_signal_unblock_sigusr2();
+```
+
+
+
+认证改变并恢复信号隐藏
+
+```cpp
+ClientAuthentication(port);
+gs_signal_recover_mask(old_sigset);
+```
+
+
+
+认证结束,恢复环境量(禁用timeout, log)
+
+```cpp
+if (!disable_sig_alarm(true))
+ ereport(FATAL, (errmsg("could not disable timer for authorization timeout")));
+
+ if (u_sess->attr.attr_storage.Log_connections) {
+ if (AM_WAL_SENDER)
+ ereport(LOG, (errmsg("replication connection authorized: user=%s", port->user_name)));
+ else
+ ereport(LOG, (errmsg("connection authorized: user=%s database=%s", port->user_name, port->database_name)));
+ }
+ if (AM_WAL_DB_SENDER) {
+ Oid userId = get_role_oid(port->user_name, false);
+ CheckLogicalPremissions(userId);
+ }
+```
+
+
+
+更新用户登录计数器
+
+```cpp
+if (IsUnderPostmaster && !IsBootstrapProcessingMode() && !dummyStandbyMode)
+ InstrUpdateUserLogCounter(true);
+
+ set_ps_display("startup", false);
+
+ u_sess->ClientAuthInProgress = false; /* client_min_messages is active now */
+ u_sess->misc_cxt.authentication_finished = true;
+```
+
+
+
+#### 读取并加载配置信息
+
+**执行函数:load_hba( )**
+
+src\common\backend\libpq\hba.cpp: 1729-1829
+
+读取配置文件:
+
+```cpp
+ file = AllocateFile(g_instance.attr.attr_common.HbaFileName, "r");
+ linecxt = tokenize_file(g_instance.attr.attr_common.HbaFileName, file, &hba_lines, &hba_line_nums);
+ (void)FreeFile(file);
+```
+
+
+
+解析所有行:
+
+```cpp
+hbacxt = AllocSetContextCreate(g_instance.instance_context,
+ "hba parser context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE,
+ SHARED_CONTEXT);
+ oldcxt = MemoryContextSwitchTo(hbacxt);
+ forboth(line, hba_lines, line_num, hba_line_nums)
+ {
+ HbaLine* newline = NULL;
+
+ if ((newline = parse_hba_line((List*)lfirst(line), lfirst_int(line_num))) == NULL) {
+ MemoryContextReset(hbacxt);
+ new_parsed_lines = NIL;
+ ok = false;
+ continue;
+ }
+
+ new_parsed_lines = lappend(new_parsed_lines, newline);
+ }
+```
+
+
+
+
+
+#### 查配置信息有效性并获取认证方式
+
+**执行函数:hba_getauthmethod( )**
+
+src\common\backend\libpq\hba.cpp: 2142-2159
+
+决定何种认证方式将被使用当访问数据库时
+
+```cpp
+void hba_getauthmethod(hbaPort* port)
+{
+#ifdef ENABLE_MULTIPLE_NODES
+ if (IsDSorHaWalSender() ) {
+#else
+ if (IsDSorHaWalSender() && (is_node_internal_connection(port) || AM_WAL_HADR_SENDER)) {
+#endif
+ check_hba_replication(port);
+ } else {
+ check_hba(port);
+ }
+}
+```
+
+
+
+#### 根据认证方式获取认证凭证并完成认证
+
+**执行函数:crypt_verify( )**
+
+src\common\backend\libpq\crypt.cpp: 374-714
+
+对加密方式进行确认
+
+包括MD5、SHA256、SM3加密方式,这里会在另一篇文章
+
+《openGauss安全认证机制之口令存储》里详细介绍
+
+#### 完成认证并加载信息
+
+**执行函数:InitUser( )**
+
+src\common\backend\utils\init\postinit.cpp 2252-2268
+
+```cpp
+void PostgresInitializer::InitUser()
+{
+ InitializeSessionUserId(m_username, false, m_useroid);
+ m_isSuperUser = superuser();
+ u_sess->misc_cxt.CurrentUserName = u_sess->proc_cxt.MyProcPort->user_name;
+#ifndef ENABLE_MULTIPLE_NODES
+ /*
+ * In opengauss, we allow twophasecleaner to connect to database as superusers
+ * for cleaning up temporary tables. During the cleanup of temporary tables,
+ * m_username and u_sess->proc_cxt.MyProcPort->user_name point to the same memory
+ * address. We have freed and reinitialized u_sess->proc_cxt.MyProcPort->user_name
+ * in function InitializeSessionUserId, and we need to initialize m_username here.
+ */
+ if (u_sess->proc_cxt.IsInnerMaintenanceTools)
+ m_username = u_sess->proc_cxt.MyProcPort->user_name;
+#endif
+}
+```
+
+
+
+
+
+
+
+### 总结
+
+不难看出,角色管理的各个方面与角色创建的操作几乎一模一样,这是遵循了对角色操作的规范性,避免因为代码的逻辑问题造成一些权限混乱,角色界限不清晰的问题。
+
+身份认证作为数据库访问安全的核心,防范未授权访问,实现严谨的身份验证方法。
+
+本文大致描述了身份认证的全过程,但并未展现认证细节。对于认证的各个方面,我们将在其他系列文章中进行一一展示。
\ No newline at end of file
diff --git "a/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\350\256\244\350\257\201\346\234\272\345\210\266.md" "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\350\256\244\350\257\201\346\234\272\345\210\266.md"
new file mode 100644
index 0000000000000000000000000000000000000000..f70eee66846e7b0843f00c209f50613a94a82d0d
--- /dev/null
+++ "b/TestTasks/KuanXY/openGauss\345\256\211\345\205\250\350\256\244\350\257\201\346\234\272\345\210\266.md"
@@ -0,0 +1,465 @@
+# openGauss安全认证机制
+
+openGauss是一款开源的**自治安全数据库**,保障用户数据的安全和隐私,是数据库最基本的功能之一。特别是在大数据时代,随着互联网的发展,信息的传播和获取越来越方便,隐私泄露、数据丢失及信息篡改等问题也愈发严重,只有维护好数据库的安全,才能为用户提供安全的服务。
+
+安全认证是客户端与服务端相互认证,建立信任连接,业务正常开展的基础,是数据库对外提供的的第一道防线,下面就看一下openGauss的安全认证机制。
+
+***
+
+#### 安全认证协议
+
+**安全认证**:openGauss采用的认证方案为**RFC5802认证协议**。RFC5802认证协议实际上是SCRAM(Salted Challenge Response Authentication Mechanism,是指 `Salted质询响应身份验证机制` 或者 `基于盐值的质询响应身份验证` 机制)标准流程中的协议。SCRAM 是一套包含**服务器和客户端双向确认的用户认证体系**,配合信道绑定可以避免中间人攻击。
+
+**openGauss的支持的认证方法**:
+
+| 认证方法 | 说明 |
+| -------- | ------------------------------------------------------------ |
+| trust | openGauss无条件接收连接请求,且访问请求时无须提供口令;当前仅支持超级用户本地登录采用此方式 |
+| 口令认证 | 主要支持sha256加密口令认证。由于整个身份认证过程中,不需要还原明文口令,因此采用 PBKDF2单向加密算法。其中 Hash函数使用sha256算法,盐值salt则通过安全随机数生成。(迭代次数可由用户决定)。非超级用户在访问登录时必须提供口令信息(用户的口令信息被存放在系统表pg_authid中的rolpassword字段中,如果为空,则表示出现元信息错误) |
+| cert认证 | openGauss支持使用SSL安全连接通道,cert认证表示使用SSL客户端进行认证,不需要提供用户密码。在该认证模式下,客户端和服务端数据经过加密处理。 |
+| gss | 使用基于gssapi的kerberos认证,该认证方法依赖kerberosserver组件,一般用于支持`openGauss集群`内部通信认证和外部客户端连接认证,外部客户端仅支持gsql(openGauss提供的在命令行下运行的数据库连接工具)或JDBC连接时使用。 |
+
+***
+
+#### 基于口令认证的密钥计算代码
+
+**基于口令认证的密钥计算代码**如下所示:
+
+```c++
+SaltedPassword := Hi(password, salt, iteration_count)
+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
+```
+
+其中
+
+- **Hi()**:本质上是PBKDF2,即将salted hash进行多次重复计算(计算次数可选择)。
+- **HMAC()**:HMAC(Hash-based Message Authentication Code,散列信息认证码),HMAC运算利用hash算法,以一个消息M和一个密钥K作为输入,生成一个定长的消息摘要作为输出。HMAC能够提供消息完整性认证以及信源身份认证。
+- **sha256()**:采用sha256算法对任意长度的消息进行散列,生成一个256bit长的消息摘要(哈希值)。
+
+| 对象 | 中文 | 解释 |
+| --------------- | ------------ | ------------------------------------------------------------ |
+| password | 密码 | 用户密码 |
+| SaltedPassword | 加盐哈希密码 | 通过用户密码,盐值和迭代次数进行PBKDF2操作生成 |
+| ClientKey | 客户端密钥 | SaltedPassword和字符串"Client Key"通过HMAC生成 |
+| ClientSignature | 客户端签名 | 对StoredKey和随机数token进行HMAC生成 |
+| StoredKey | 存储密钥 | 对ClientKey进行sha256生成,**用以验证用户身份(服务器端存储)** |
+| ServerKey | 服务器密钥 | SaltedPassword和字符串"Sever Key"通过HMAC生成,**客户端验证服务端身份(服务器端存储)** |
+| ServerSignature | 服务器签名 | 对ServerKey和随机数token进行HMAC生成 |
+| ClientProof | 客户端证明 | 对ClientSignature和ClientKey进行异或生成 |
+
+生成过程图如下所示:
+
+
+
+
+图中橙色块`ServerKey`和`StoredKey`是用户创建或修改用户属性(密码)时就会通过加密函数计算得到的,并被拼接为特定字符串存储于服务端。
+
+绿色块`ClientSignature`、`ServerSignature`和`ClientProof`在每次建立连接后都需要计算,由于受随机数token的影响,在每次建立连接中值都不同。
+
+***
+
+#### 认证关键点
+
+1. **服务器通过StoredKey验证客户端用户身份**
+
+ClientProof(客户端证明)由ClientSignature和ClientKey进行异或(XOR)生成。
+
+而当客户端验证时则会将`ClientSignature`和客户端发送来的`ClientProof`进行异或,恢复生成`ClientKey`,即:
+
+```c++
+ClientKey:= ClientSignature XOR ClientProof
+```
+
+再将ClinetKey进行哈希运算,将得到的值与StoredKey进行对比,如果相等则客户端认证通过。
+
+服务器计算出来的ClientKey验证完后直接丢弃。
+
+2. **客户端通过ServerKey验证服务器身份**
+
+将ServerSignature与服务器端发送来的值进行对比,相等则认证通过。
+
+***
+
+#### 认证流程
+
+认证流程图:
+
+
+认证流程:
+
+1. 首先客户端与服务器建立连接,客户端将自己的username发送给服务端。
+2. 服务端在收到username后,生成一个随机数token,并通过username查询到用户对应盐值slat,ServerKey和迭代次数,并通过ServerKey和刚刚生成的token进行HMAC得到ServerSignature。
+3. 服务端将认证方式信息,随机数token,盐值salt,ServerSignature和迭代次数发送给客户端。
+4. 客户端利用密码password,盐值salt和迭代次数通过PBKDF2生成K(即SaltedPassword),有了K,就可以分别与字符串“Client Key”和“Sever Key”进行HMAC操作,生成对应的ClientKey和ServerKey。再将其中的ClientKey进行SHA256得到StoredKey。
+5. 客户端生成对应的ServerKey和StoredKey后,将字符串“sha256”、盐值salt、ServerKey和StoredKey拼接为Buf。从Buf中得到ServerKey并和服务端刚刚生成的随机数token进行HMAC操作,得到`客户端侧生成的ServerSignature`,与服务器发送来的ServerSignature对比,一致则**客户端认证服务器通过**。
+6. 认证完服务器后,将自己的StoredKey和token进行HMAC生成ClientSignature,并再将ClientSignature和ClientKey进行异或操作,生成ClientProof。
+7. 客户端将ClientProof发送给服务端。
+8. 服务端获取ClientProof后,通过Storedkey和token的HAMC计算出ClientSignature,通过ClientProof和ClientSignature进行异或得到ClientKey,在进行HMAC得到StoredKey,与`服务端本身存储的StoredKey`进行对比,若一致,则**服务端认证客户端通过**。
+
+以上就是口令安全认证机制的具体流程。
+
+对客户端认证主要通过调用`ClientAuthentication()`函数完成,下面对该函数的源码(部分省略)进行分析和注释:
+
+***
+
+#### ClientAuthentication
+
+接收的参数为[Port结构体](https://forum.gitlink.org.cn/forums/8338/detail)指针,通过sendAuthRequest()函数发送请求到前端。
+
+- 第3~8行定义相关变量,包括status,随机数token等。
+- 第13行获取port中的认证方法。
+- 第20行根据用户认证方法进行对应操作。
+ - 第21行是无条件拒绝连接。
+ - 第25行是匹配失败。
+ - 第32行是MD5认证方法。
+ - 第43、44行对应SHA256认证和SM3认证。可以看出在服务端SHA256和SM3的认证基本流程一样。
+ - 第61行通过sha_bytes_to_hex8生成随机数token。
+ - 第63行通过检查hba->auth_method从而选择SHA256还是SM3进行认证。
+- 第73~77行根据status完成身份认证。
+
+>/src/common/backend/libpq/auth.cpp 350——773行
+
+```c++
+void ClientAuthentication(Port* port) /* 传入Port结构体指针参数 */
+{
+ int status = STATUS_ERROR; /* 设置认证初始状态为ERROR */
+ /* 数据库安全:支持密码复杂性 */
+ char details[PGAUDIT_MAXLENGTH] = {0};
+ char token[TOKEN_LENGTH + 1] = {0};
+ errno_t rc = EOK;
+ int retval = 0;
+
+ /*
+ 获取用于此前端/数据库组合的身份验证方法。注意:此时我们不解析文件;其他地方已经这样做了。如果解析hba配置文件失败,hba.c会将错误消息放入服务器日志文件。
+ */
+ hba_getauthmethod(port);
+
+ ······
+
+ /*
+ * 现在继续进行实际的身份验证检查
+ */
+ switch (port->hba->auth_method) { /* 根据用户认证方法执行对应操作 */
+ case uaReject: /* 无条件拒绝连接 */
+
+ ······
+
+ case uaImplicitReject:
+
+ /*
+ * 没有匹配条目,所以告诉用户我们失败了
+注意:这里报告的额外信息不是安全漏洞,因为所有这些信息都是在前端已知的,必须假定坏人已知。我们只是在帮助那些不那么神秘的好人。
+ */
+ ······
+ case uaMD5: /* 数据库安全:支持MD5认证方法 */
+ /* 禁止与初始用户远程连接 */
+ if (isRemoteInitialUser(port)) {
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("Forbid remote connection with initial user.")));
+ }
+ sendAuthRequest(port, AUTH_REQ_MD5);/* 发送认证请求到前端,认证码为AUTH_REQ_MD5 */
+ status = recv_and_check_password_packet(port);/* 接收和检查密码包,返回状态 */
+ break;
+ /* 数据库安全:支持SHA256认证方法 */
+ case uaSHA256:
+ case uaSM3: /* 数据库安全:支持SHA256和SM3认证方法 */
+ /* 禁止与初始用户远程连接 */
+ 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();
+ retval = RAND_priv_bytes((GS_UCHAR*)token, (GS_UINT32)TOKEN_LENGTH);//生成随机数token
+ RESUME_INTERRUPTS();
+ CHECK_FOR_INTERRUPTS();
+ if (retval != 1) {
+ ereport(ERROR, (errmsg("Failed to Generate the random number,errcode:%d", retval)));
+ }
+ sha_bytes_to_hex8((uint8*)token, port->token); /* 生成随机数token */
+ port->token[TOKEN_LENGTH * 2] = '\0';
+ if (port->hba->auth_method == uaSHA256) {/* 判断认证方法是SHA256还是SM3 */
+ sendAuthRequest(port, AUTH_REQ_SHA256);/* 发送认证请求到前端,认证码为AUTH_REQ_SHA256 */
+ } else {
+ sendAuthRequest(port, AUTH_REQ_SM3);/* 发送认证请求到前端,认证码为AUTH_REQ_SM3 */
+ }
+ 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;
+}
+```
+
+客户端认证服务端主要通过调用`pg_password_sendauth()`函数完成,下面对该函数的源码(部分省略)进行分析和注释:
+
+***
+
+#### pg_password_sendauth
+
+结合上述流程和图片分析代码,代码实现非常清晰明了(主要看SHA256和SM3的具体实现):
+
+- 第3~第25行初始化了一系列变量,包括上述提及的ClientProof,server_key,ClientSignature,server_signature,随机数token等。
+- 第28行switch语句根据不同的认证请求进行对应操作。(由于不支持直接发送密码的认证,代码中删除了AUTH_REQ_PASSWORD的分支)
+- 第37行开始为SHA256加密的认证。
+- 第119行开始为SM3方式加密的认证。
+- 第212~225行清空变量,保证数据安全。
+
+>/src/common/interfaces/libpq/fe-auth.cpp 770——1077行
+
+```c++
+static int pg_password_sendauth(PGconn* conn, const char* password, AuthRequest areq)
+{ /* 初始化变量 */
+ int ret;
+ char* crypt_pwd = NULL;
+ int crypt_pwd_sz = 0;
+ const char* pwd_to_send = NULL; /* 发送给服务端的信息 */
+ int hmac_length = HMAC_LENGTH; /* HMAC长度(32)*/
+ char h[HMAC_LENGTH + 1] = {0}; /* ClientProof二进制形式 */
+ char h_string[HMAC_LENGTH * 2 + 1] = {0}; /* ClientProof字符串形式 */
+ char hmac_result[HMAC_LENGTH + 1] = {0}; /* ClientSignature */
+ char client_key_bytes[HMAC_LENGTH + 1] = {0}; /* ClientKey二进制形式 */
+ char buf[SHA256_PASSWD_LEN + 1] = {0}; /* 拼接字符串buf形式 */
+ char sever_key_bytes[HMAC_LENGTH + 1] = {0}; /* server_key二进制形式 */
+ char server_key_string[HMAC_LENGTH * 2 + 1] = {0}; /* server_key字符串形式 */
+ char token[TOKEN_LENGTH + 1] = {0}; /* 随机数token */
+ char client_sever_signature_bytes[HMAC_LENGTH + 1] = {0}; /* 客户端生成的server_signature二进制形式 */
+ char client_sever_signature_string[HMAC_LENGTH * 2 + 1] = {0}; /* 客户端生成的server_signature字符串形式 */
+ char salt[SALT_LENGTH + 1] = {0}; /* 盐值,SALT_LENGTH=32 */
+ char stored_key_bytes[STORED_KEY_LENGTH + 1] = {0}; /* storedKey二进制形式 */
+ char stored_key_string[STORED_KEY_LENGTH * 2 + 1] = {0}; /* storedKey字符串形式 */
+ char client_key_buf[CLIENT_KEY_STRING_LENGTH + 1] = {0}; /* ClientKey字符串形式 */
+ char fail_info[] = "sever_signature_failed"; /* 失败信息 */
+ int CRYPT_hmac_ret1; /* 状态:生成客户端server_signature是否成功 */
+ int CRYPT_hmac_ret2; /* 状态:生成H(ClientProof)是否成功 */
+ errno_t rc = 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;
+
+ ······
+
+#else
+ if (SHA256_PASSWORD == conn->password_stored_method || PLAIN_PASSWORD == conn->password_stored_method) {
+ /* 通过SHA256方式加密,生成sha256加密字符串
+ pg_sha_encrypt输入密码,盐值salt,迭代次数,得到buf*/
+ if (!pg_sha256_encrypt(
+ password, conn->salt, strlen(conn->salt), (char*)buf, client_key_buf, conn->iteration_count))
+ return STATUS_ERROR; /* 加密失败则返回错误 */
+#endif
+ rc = strncpy_s(server_key_string, /* 将buf内容的ServerKey部分复制到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, /* 将buf内容的StoredKey部分复制到stroed_key_stirng中 */
+ 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'; /* 转C字符串 */
+ stored_key_string[sizeof(stored_key_string) - 1] = '\0'; /* 转C字符串 */
+
+
+ sha_hex_to_bytes32(sever_key_bytes, server_key_string);/* 将server_key_string字符串(64字节)转换为二进制(32字节)*/
+ sha_hex_to_bytes4(token, conn->token); /* token将字符串(8字节)转换为二进制(4字节) */
+ /* 通过server_key和token调用HMAC算法计算,得到client_server_signature_bytes,将该变量转为字符串变量,用来验证与服务端传来的server_signature是否相等*/
+ CRYPT_hmac_ret1 = CRYPT_hmac(NID_hmacWithSHA256,
+ (GS_UCHAR*)sever_key_bytes,
+ HMAC_LENGTH,
+ (GS_UCHAR*)token,
+ TOKEN_LENGTH,
+ (GS_UCHAR*)client_sever_signature_bytes,
+ (GS_UINT32*)&hmac_length);
+ if (CRYPT_hmac_ret1) {
+ return STATUS_ERROR;
+ }
+
+ sha_bytes_to_hex64((uint8*)client_sever_signature_bytes, client_sever_signature_string); /* 将客户端生成的server_signature二进制(32字节)转换为字符串(64字节)*/
+
+ /*
+ * 在检查客户端签名不安全之前,请检查服务器签名。
+ * 未来:rfc5802认证协议需要增强。
+ * 调用函数strncmp判断计算的client_server_signature_string和服务端传来的server_signature值是否相等
+ */
+ if (PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_GAUSS_BASE &&
+ 0 != strncmp(conn->sever_signature, client_sever_signature_string, HMAC_STRING_LENGTH)) { /* 如果客户端生成的server_signature与服务器发送过来的server_signature不相等则给pwd_to_send赋值失败信息 */
+ pwd_to_send = fail_info;
+ } else {
+ /* 计算H(ClientProof),H=hmac(storedkey,token)XOR ClientKey*/
+ sha_hex_to_bytes32(stored_key_bytes, stored_key_string); /* 将storedKey字符串(64字节)转换为二进制(32字节)*/
+ 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) { /* H计算错误返回失败状态 */
+ return STATUS_ERROR;
+ }
+
+ sha_hex_to_bytes32(client_key_bytes, client_key_buf); /*将client_key_buf字符串(64字节)转换为client_key_bytes二进制(32字节) */
+ /* 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); /* 将h(ClientProof)二进制(32字节)转换为字符串(64字节) */
+
+ /*将H(ClientProof)发送到服务器*/
+ pwd_to_send = h_string;
+ }
+ }
+
+······
+ break;
+ }
+ case AUTH_REQ_SM3: {
+ /* 通过SM3方式加密 */
+ if (conn->password_stored_method == SM3_PASSWORD) {
+ if (!GsSm3Encrypt(
+ password, conn->salt, strlen(conn->salt), (char*)buf, client_key_buf, conn->iteration_count))
+ return STATUS_ERROR; /* 加密失败则返回错误 */
+ /* 将buf内容的ServerKey部分复制到server_key_string中 */
+ rc = strncpy_s(server_key_string,
+ sizeof(server_key_string),
+ &buf[SM3_LENGTH + SALT_STRING_LENGTH],
+ sizeof(server_key_string) - 1);
+ securec_check_c(rc, "\0", "\0");
+ /* 将buf内容的StoredKey部分复制到stroed_key_stirng中 */
+ rc = strncpy_s(stored_key_string,
+ sizeof(stored_key_string),
+ &buf[SM3_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'; /* 转C字符串 */
+ stored_key_string[sizeof(stored_key_string) - 1] = '\0'; /* 转C字符串 */
+ /* 将server_key_string字符串(64字节)转换为二进制(32字节)*/
+ sha_hex_to_bytes32(sever_key_bytes, server_key_string);
+ /* token将字符串(8字节)转换为二进制(4字节) */
+ 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*)sever_key_bytes,
+ HMAC_LENGTH,
+ (GS_UCHAR*)token,
+ TOKEN_LENGTH,
+ (GS_UCHAR*)client_sever_signature_bytes,
+ (GS_UINT32*)&hmac_length);
+ if (CRYPT_hmac_ret1) {
+ return STATUS_ERROR;
+ }
+ /* 将客户端生成的server_signature二进制(32字节)转换为字符串(64字节)*/
+ sha_bytes_to_hex64((uint8*)client_sever_signature_bytes, client_sever_signature_string);
+
+ /*
+ * 在检查客户端签名不安全之前,请检查服务器签名。
+ * 计算H(ClientProof),H=hmac(storedkey,token)XOR ClientKey
+ */
+ if (PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_GAUSS_BASE &&
+ 0 != strncmp(conn->sever_signature, client_sever_signature_string, HMAC_STRING_LENGTH)) {/* 如果客户端生成的server_signature与服务器发送过来的server_signature不相等则给pwd_to_send赋值失败信息 */
+ pwd_to_send = fail_info;
+ } else {
+ /* 计算 H(ClientProof), H = hmac(storedkey, token) XOR ClientKey */
+ sha_hex_to_bytes32(stored_key_bytes, stored_key_string); /* 将storedKey字符串(64字节)转换为二进制(32字节)*/
+ 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; /* H计算错误返回失败状态 */
+ }
+
+ 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;
+ }
+ /* 将h转化为string */
+ sha_bytes_to_hex64((uint8*)h, h_string);
+
+ /* 将H发送到服务器 */
+ pwd_to_send = h_string;
+ }
+ } else {
+ pwd_to_send = password;
+ }
+
+ break;
+ }
+/*
+ * 注意:目前不支持直接发送密码的身份验证。
+ * 需要:出于默认和安全原因,我们在这里删除AUTH_REQ_PASSWORD分支。
+ */
+ default:
+ return STATUS_ERROR;
+ }
+ /* 从协议3.0开始,数据包具有消息类型 */
+ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+ ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1);
+ else
+ ret = pqPacketSend(conn, 0, pwd_to_send, strlen(pwd_to_send) + 1);
+ if (crypt_pwd != NULL) {
+ erase_mem(crypt_pwd, crypt_pwd_sz);
+ libpq_free(crypt_pwd);
+ }
+ /* 清空变量,保证安全 */
+ erase_arr(h_string);
+ erase_arr(h);
+ erase_arr(hmac_result);
+ erase_arr(client_key_bytes);
+ erase_arr(buf);
+ erase_arr(sever_key_bytes);
+ erase_arr(server_key_string);
+ erase_arr(token);
+ erase_arr(client_sever_signature_bytes);
+ erase_arr(client_sever_signature_string);
+ erase_arr(salt);
+ erase_arr(stored_key_bytes);
+ erase_arr(stored_key_string);
+ erase_arr(client_key_buf);
+
+ return ret;
+}
+```
+
+> 对于**SM3国密算法**的解析参见我们的另一篇文章——《[openGauss新增国密SM3加密算法解析](https://forum.gitlink.org.cn/forums/8337/detail)》
+
+***
+
+#### 总结
+
+本文介绍了openGauss的安全认证机制,openGauss采用的是**基于盐值的质询响应身份验证**机制进行安全认证。认证过程中客户端和服务器需要双向认证,服务端不会获取和保存密码明文,盐值与多重哈希算法保证了用户密码不会泄露,防止彩虹攻击,随机数token保证了每次认证中发送的证明都不一样,从而防止重放攻击。
+
+从源码中我们可以清晰地了解到认证机制的具体实现,其中涉及一些二进制与字符串格式的转换,通过源码也了解到认证过程中openGauss支持SM3国密算法的使用,openGauss的安全性正在不断增强。
\ No newline at end of file
diff --git "a/TestTasks/KuanXY/openGauss\346\235\203\351\231\220\347\256\241\347\220\206.md" "b/TestTasks/KuanXY/openGauss\346\235\203\351\231\220\347\256\241\347\220\206.md"
new file mode 100644
index 0000000000000000000000000000000000000000..bc1c14906b212115c9483b319323b4a8d1a0c424
--- /dev/null
+++ "b/TestTasks/KuanXY/openGauss\346\235\203\351\231\220\347\256\241\347\220\206.md"
@@ -0,0 +1,1038 @@
+# openGauss权限管理
+## 权限管理概述
+
+> 什么是权限?为什么需要权限?
+
+在各类系统中,权限都非常重要,它定义了每个用户可以进行的行为及范围。在openGauss数据库中,用户执行任何命令都需拥有对应的权限,通过合理地分配和回收权限保护数据库的正常使用,防止用户越级滥用权限。
+
+openGauss数据库权限可以分为**系统权限**和**对象权限**两种。
+
+数据库安装过程生成的**初始用户**拥有最高权限(即超级用户),可以执行所有操作。(初始用户会绕过所有权限检查,因此openGauss建议仅将此初始用户作为DBA管理用途,而非业务应用。)
+
+### 系统权限
+
+| 系统权限 |