openGauss数据库是一款标准的基于客户端/服务端(C/S)模式工作的数据库系统,每一个完整的会话连接都由后台服务进程和客户端进程组成。一个完整的客户端认证过程如图所示。
openGauss认证详细流程
(1) 客户端依据用户需求配置相关认证信息,这里主要指SSL(Secure Sockets Layer,安全套接层)认证相关信息,建立与服务端之间的连接。
(2) 连接建立完成后,客户端发送访问所需要的连接请求信息给服务端,对请求信息的验证工作都在服务端完成。
(3) 服务端首先需要进行访问源的校验,即依据配置文件对访问的端口号、访问IP地址、允许用户访问范围以及访问数据对象进行校验。
(4) 在完成校验后连同认证方式和必要的信息返回给客户端。
(5) 客户端依据认证方式加密口令并发送认证所需的信息给服务端。
(6) 服务端对收到的认证信息进行认证。认证通过,则启动会话任务与客户端进行通信提供数据库服务。否则拒绝当前连接,并退出会话。
客户端安全认证机制是openGauss数据库的第一层安全保护机制,解决了访问源与数据库服务端间的信任问题。通过这层机制可有效拦截非法用户对数据库进行恶意访问,避免后续的非法操作。
一、客户端配置信息
如前面所描述的,安全认证机制首先要解决访问源可信的问题。openGauss通过系统配置将访问方式、访问源IP地址(客户端地址)以及认证方法存放在服务端的配置文件中。与这些信息同时存放的还包括数据库名、用户名。这些信息会组成一条认证记录,存放在配置文件(Host-Based Authentication File,HBA文件)中。HBA文件记录的格式可为如下四种格式中的一种:
local DATABASE USER METHOD [OPTIONS]
host DATABASE USER ADDRESS METHOD [OPTIONS]
hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
hostnossl DATABASE USER ADDRESS METHOD [OPTIONS]
一个HBA文件中可以包含多条记录,一条记录不能跨行存在,每条记录内部是由若干个空格、“/”和制表符分隔的字段组成。在实际认证过程中,身份认证模块需要依据HBA文件中记录的内容对每个连接请求进行检查,因此记录的顺序是非常关键的。每一条记录中各个字段的具体含义如下所述:
- local:表示这条记录只接受通过Unix域套接字进行的连接。没有这种类型的记录,就不允许Unix域套接字的连接。只有在从服务器本机连接且在不指定-U参数的情况下,才是通过Unix域套接字连接。
- host:表示这条记录既接受一个普通的TCP/IP套接字连接,也接受一个经过SSL加密的TCP/IP套接字连接。
- hostssl:表示这条记录只接受一个经过SSL加密的TCP/IP套接字连接。
- hostnossl:表示这条记录只接受一个普通的TCP/IP套接字连接。
- DATABASE:声明当前记录所匹配且允许访问的数据库。特别地,该字段可选用all、sameuser以及samerole。其中all表示当前记录允许访问所有数据库对象;sameuser表示访问的数据库须与请求的用户同名才可访问;samerole表示访问请求的用户必须是与数据库同名角色中的成员才可访问。
- USER:声明当前记录所匹配且允许访问的数据库用户。特别地,该字段可选用all以及“+角色(角色组)”。其中all表示允许所有数据库用户对象访问;“+角色(角色组)”表示匹配任何直接或间接(通过继承的方式)这个角色(角色组)的成员。
- ADDRESS:指定与记录匹配且允许访问的IP地址范围。目前支持IPv4和IPv6两种形式的地址。
- METHOD:声明连接时所使用的认证方法。目前openGauss所支持的认证方式包括trust、reject、sha256、cert以及gss。我们将在11.2.2小节着重介绍。
- OPTIONS:这个可选字段的含义取决于选择的认证方法。目前作为保留项方便后续认证方式扩展,如支持基于ident认证时需要指定映射选项。
在openGauss数据库系统安装部署完成后,HBA文件中默认包含了超级用户的配置记录。对于其他管理员新创建的用户则需要重新进行配置。对于认证规则的配置建议遵循如下基本原则:
- 靠前的记录有比较严格的连接参数和比较弱的认证方法。
- 靠后的记录有比较宽松的连接参数和比较强的认证方法。
openGauss除了支持手工配置认证信息外,还支持使用GUC(Grand Unified Configuration)工具进行规则配置,如允许名为jack的用户在客户端工具所在的IP地址为122.10.10.30的地方以sha256方式登录服务端数据库database1:
gs_guc set -Z coordinator -N all -I all -h "host database1 jack 122.10.10.30/32 sha256"
这条命令将在所有的CN实例侧对应的HBA文件中添加对应规则。
二、服务端认证方法
openGauss安全认证的认证方法在HBA文件中由数据库运维人员配置,支持的认证方法包括trust认证、口令认证和cert认证。
1. trust认证
trust认证意味着采用当前认证模式时,openGauss无条件接受连接请求,且访问请求时无需提供口令。这个方法如果使用不当,可允许所有用户在不提供口令的情况下直接连入数据库。为保障安全,openGauss当前仅支持数据库超级用户在本地以trust方式登录,不允许远程连接使用trust认证方法。
2. 口令认证
openGauss目前主要支持sha256加密口令认证。由于整个身份认证过程中,不需要还原明文口令,因此采用PBKDF2单向加密算法。其中hash函数使用sha256算法,盐值则通过安全随机数生成。算法中涉及的迭代次数可由用户自己视不同的场景决定,需考虑安全和性能间的一个平衡。
为了保留对历史版本的兼容性,在某些兼容性场景下,openGauss还支持MD5算法对口令进行加密,但默认不推荐。
特别地,openGauss数据库管理员用户在创建用户信息时不允许创建空口令,这意味着非超级用户在访问登录时必须提供口令信息(命令方式或交互式方式)。用户的口令信息被存放在系统表pg_authid中的rolpassword字段中,如果为空则表示出现元信息错误。
3. cert认证
openGauss支持使用SSL安全连接通道。cert认证表示使用SSL客户端进行认证,不需要提供用户密码。在该认证模式下,客户端和服务端数据经过加密处理。在连接通道建立后,服务端会发送主密钥信息给客户端以响应客户端的握手信息,这个主密钥将是服务端识别客户端的重要依据。值得注意的是,该认证方式只支持hostssl类型的规则。
在前面,我们提到openGauss所支持的METHOD字段选项包括trust、reject、sha256、cert以及gss。除去上述介绍的三种认证方式外,reject选项表示对于当前认证规则无条件拒绝,一般用于“过滤”某些特定的主机。gss表示使用基于gssapi的kerberos认证,该认证方式依赖kerberos server组件,一般用于支持openGauss集群内部通信认证和外部客户端连接认证,外部客户端仅支持gsql或JDBC连接时使用。
三、安全认证通道
首先openGauss支持SSL标准协议(TLS1.2),SSL协议是安全性更高的协议标准,它们加入了数字签名和数字证书来实现客户端和服务器的双向身份验证,保证了通信双方更加安全的数据传输。
openGauss在安装部署完成后,默认开启SSL认证模式。安装包中也包含了认证所需要的证书和密钥信息。这些证书由CA可信中心颁发。假定服务器的私钥为server.key,证书为server.crt,客户端的私钥为client.key,证书为client.crt,CA根证书名称为cacert.pem。这些证书信息存放在“/home/ommdbadmin”目录。
需要说明的是,集群安装部署完成后,服务端证书、私钥以及根证书均已默认配置完成。用户只需要配置客户端相关的参数。
1. 配置客户端参数
客户端参数配置依据实际场景分为单向认证配置和双向认证配置,整个配置信息存储在客户端工具所在的环境配置文件中(如.bashrc文件)。单向认证需要配置如下参数:
export PGSSLMODE="verify-ca"
export PGSSLROOTCERT="/home/ommdbadmin/cacert.pem"
双向认证需配置如下参数:
export PGSSLCERT="/home/ommdbadmin/client.crt"
export PGSSLKEY="/home/ommdbadmin/client.key"
export PGSSLMODE="verify-ca"
export PGSSLROOTCERT="/home/ommdbadmin/cacert.pem"
2. 修改客户端密钥的权限
客户端根证书、密钥、证书以及密钥密码加密文件的权限,需保证为600。如果权限不满足要求,则客户端无法以SSL连接到集群。
chmod 600 cacert.pem
chmod 600 client.key
chmod 600 client.crt
chmod 600 client.key.cipher
chmod 600 client.key.rand
在实际应用中,应结合场景进行配置。从安全性考虑,建议使用双向认证方式,此时客户端的PGSSLMODE变量建议设置为verify-ca。但如果本身数据库处在一个安全的环境下,且业务场景属于高并发、低时延业务则可使用单向认证模式。
除了通过SSL进行安全的TCP/IP连接外,openGauss还支持SSH隧道进行安全的TCP/IP连接。SSH专为远程登录会话和其他网络服务提供安全性的协议。从SSH客户端来看,SSH提供了两种级别的安全验证:
- 基于口令的安全验证:使用帐号和口令登录到远程主机。所有传输的数据都会被加密,但是不能保证正在连接的服务器就是需要连接的服务器。可能会有其他服务器冒充真正的服务器,也就是受到“中间人”方式的攻击。
- 基于密钥的安全验证:用户必须为自己创建一对密钥,并把公钥放在需要访问的服务器上。这种级别的认证不仅加密所有传送的数据,而且避免“中间人”攻击方式。但是整个登录的过程可能需要10秒。
在实际执行过程中,SSH服务和数据库服务应运行在同一台服务器上。
四、RFC5802认证协议
在实际应用过程中,仅仅选定认证方法是不够的,还需要用一套完整的认证机制。这个机制要可以很好的解决客户端和服务端认证交互过程中的通信风险,还要解决客户端接收到加密口令后的验证问题。
openGauss目前选用标准的RFC5802认证机制来解决相关问题。它实际上是SCRAM(Salted Challenge Response Authentication Mechanism)标准流程中的协议。SCRAM是一套包含服务器和客户端双向确认的用户认证体系,配合信道绑定可以避免中间人攻击。下面我们将着重介绍该协议内容。
- 首先,客户端知道用户名username和密码password,客户端发送用户名username给服务端,服务端检索相应的认证信息,例如:salt、StoredKey、ServerKey和迭代次数i(注意,服务端可能对于所有的用户都是用相同的迭代次数)。然后,服务端发送盐值salt和迭代次数i给客户端。
- 接下来,客户端需要进行一些计算,给服务端发送ClientProof认证信息,服务端通过ClientProof对客户端进行认证,并发送ServerSignature给客户端。
- 最后,客户端通过ServerSignature对服务端进行认证。
具体密钥计算公式如下:
SaltedPassword := Hi (password, salt, i) 其中,Hi()本质上是PBKDF2。
ClientKey := HMAC(SaltedPassword, "Client Key")
StoredKey := Hash(ClientKey)
AuthMessage := client-first-message-bare + "," +
server-first-message + "," +
client-final-message-without-proof
ServerKey := HMAC(SaltedPassword, "Server Key")
其中:
- AuthMessage通过连接认证交换的信息来计算的。
- client-first-message-bare主要包含客户端给服务端发送的用户名username和随机字符串C-Nonce。
- server-first-message主要是盐值salt、迭代次数i以及随机生成的字符串Nonce。
- client-final-message-without-proof不包含认证信息ClientProof,包含随机字符串Nonce。
具体密钥衍生过程如图所示
密钥衍生过程
在这里,服务器端存的是StoredKey和ServerKey。
- StoredKey是用来验证Client客户身份,服务端认证客户端。通过计算ClientSignature与客户端发来的ClientProof进行异或运算,从而恢复得到ClientKey,然后将其进行hash运算,将得到的值与StoredKey进行对比。如果相等,证明客户端验证通过。
- ServerKey是用来向客户端表明自己身份的,客户端认证服务端。通过计算ServerSignature与服务端发来的值进行比较,如果相等,则完成对服务端的认证。
在认证过程中,服务端可以计算出来ClientKey,验证完后直接丢弃不必存储。防止服务端伪造认证信息ClientProof,从而仿冒客户端。要做到合法的登录,必须知道Password、SaltedPassword或者ClientKey。如果StoryKey和ServerKey泄露,无法做到合法登录。
下图描述在一个认证会话期间的客户端和服务端的详细信息交换过程:
服务端、客户端认证标准流程
(1) 客户端发送username和随机生成的挑战值C-Nonce给服务端。
(2) 服务端返回盐值salt、迭代次数i以及随机生成的挑战值Nonce给客户端。Nonce是将从客户端收到的C-Nonce和随机生成字符串组合形成的新挑战值。
(3) 客户端发送认证响应。响应信息包含客户端认证信息ClientProof和挑战值Nonce。ClientProof证明客户端拥有ClientKey,但是不通过网络的方式发送。在收到信息后,首先需要校验传来的挑战值Nonce,校验通过后,计算ClientProof。
客户端利用salt和iteration-count(迭代次数),从password计算得到SaltedPassword,然后通过密钥计算公式计算得到ClientKey、StoryKey和ServerKey。计算AuthMessage、ClientSignature。通过将客户端首次发送的信息,服务端首次发送的信息以及客户端的响应信息(不包含认证信息)连接起来得到AuthMessage。
AuthMessage := client-first-message-bare + "," +server-first-message + "," + client-final-message-without-proof
ClientSignature := HMAC(StoredKey, AuthMessage)
客户端通过将ClientKey和ClientSignature进行异或得到ClientProof:
ClientProof := ClientKey XOR ClientSignature
将计算得到的ClientProof和第2步接收的随机字符串Nonce发送给服务端进行认证。
(4) 服务端认证Nonce和ClientProof,并且发送自己的认证信息ServerSignature。首先需要校验Nonce,校验通过后,计算ServerSignature。使用其保存的StoredKey和AuthMessage通过HMAC(Hash Message Authentication Code)算法进行计算,然后与客户端传来的ClientProof进行异或,恢复ClientKey,再对ClientKey进行哈希计算,得到的结果与服务端保存的StoredKey进行比较。如果相等,则服务端对客户端的认证通过。
ClientSignature := HMAC(StoredKey, AuthMessage)
H(ClientProof XOR ClientSignature ) == StoredKey
(5) 服务端通过计算得到的ServerSignature返回给客户端。
ServerSignature := HMAC(ServerKey, AuthMessage)
(6) 客户端通过将ServerKey和AuthMessage进行HMAC计算得到的ServerSignature与服务端传来的ServerSignature进行比较。如果相等,则客户端完成对服务端的认证。