学习 探索 分享数据库知识和技术 共建数据库技术交流圈
9.1 安全管理整体架构和代码概览
不同于数据库其他业务模块,安全管理模块并非逻辑集中的。安全管理模块中的安全能力是分散化的,在数据库整个业务逻辑的不同阶段提供对应的安全能力,从而构建数据库整体纵深安全防御能力。一个完整的安全管理整体架构如图1所示。
图1 openGauss安全机制体系
1. 认证机制
认证机制子模块在业务流程上主要包括认证配置文件管理、用户身份识别、口令校验等过程,其核心流程及接口定义如图2所示。
图2 openGauss安全认证代码接口
2. 用户角色管理
用户角色管理子模块在业务流程上主要包括角色创建、修改、删除、授权和回收。由于openGauss并未严格区分用户和角色,因此用户的管理与角色管理共用一套接口,仅在部分属性上进行区分。角色管理子模块涉及的功能及其对应的接口如图3所示。
图3 openGauss角色管理代码接口
3. 对象访问控制
对象访问控制子模块在业务流程上主要包括对象授权、对象权限回收以及实际对象操作时的对象权限检查,其核心流程及接口定义如图4所示。
图4 openGauss对象权限管理代码接口
4. 审计机制
审计机制子模块主要包括审计日志的创建和管理以及数据库的各类管理活动和业务活动的审计追溯。审计日志管理包括新创建审计日志、审计日志轮转、审计日志清理。审计日志追溯包括活动发生时的日志记录以及审计信息查询接口。其核心流程及接口定义如图5所示。
9.2 安全认证
9.2.1 身份认证
hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
typedef struct HbaLine
{
int linenumber; /* 规则行号 */
ConnType conntype; /* 连接套接字方法 */
List* databases; /* 允许访问的数据库集合*/
List* roles; /* 允许访问的用户组 */
…
char* hostname; /* 允许访问的IP地址 */
UserAuth auth_method; /* 认证方法 */
…
} HbaLine;
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;
/**扫描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;
}
9.2.2 口令存储
创建用户和修改用户属性的函数入口分别为CreateRole和AlterRole。在函数内对口令加密前会先校验是否满足口令复杂度,如果满足则调用calculate_encrypted_password函数实现口令的加密。加密时根据参数password_encryption_type配置选择对应的加密方式,加密完成后会清理内存中的敏感信息并返回口令密文。口令加密流程如图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函数执行流程