PostgreSQL具有原生支持使用SSL连接来加密客户端/服务器通信, 使用TLS协议以提高安全性。有关服务器端SSL功能的详细信息,请参见第 19.9 节。
libpq读取系统范围的OpenSSL配置文件。默认情况下,这个文件被命名为openssl.cnf
并且位于openssl version -d
所报告的目录中。可以通过设置环境变量OPENSSL_CONF
把这个默认值覆盖为想要的配置文件的名称。
默认情况下,PostgreSQL将不会执行服务器证书的任何验证。这意味着可以在不被客户端知晓的情况下伪造服务器身份(例如通过修改一个 DNS 记录或者接管服务器的 IP 地址)。为了阻止哄骗,客户端必须能够通过一条信任链验证服务器的身份。信任链可以这样建立:在一台计算机上放置一个根(自签名的)证书机构(CA)的证书并且在另一台计算机上放置一个由根证书签发的叶子证书。还可以使用一种“中间”证书,它由根证书签发并且可以签发叶子证书。
为了允许客户端验证服务器的身份,在客户端上放置一份根证书并且在服务器上放置由根证书签发的叶子证书。为了允许服务器验证客户端的身份,在服务器上放置一份根证书并且在客户端上放置由根证书签发的叶子证书。也可以使用一个或者更多个中间证书(通常与叶子证书存在一起)来将叶子证书链接到根证书。
一旦信任链被建立起来,客户端有两种方法验证服务器发过来的叶子证书。如果参数sslmode
被设置为verify-ca
,libpq将通过检查该证书是否链接到存储在客户端上的根证书来验证服务器。如果sslmode
被设置为verify-full
,libpq还将验证服务器的主机名匹配存储在服务器证书中的名称。如果服务器证书无法被验证,则SSL连接将失败。在大部分对安全性很敏感的环境中,推荐使用verify-full
。
在verify-full
模式下,主机名将与证书的主体替代名称属性(SAN)匹配,
或者与通用名称属性匹配,如果没有类型为dNSName
的SAN存在。
如果证书的名称属性以星号(*
)开头,那么星号将被视为通配符,
可以匹配所有字符,除了点(.
)。
这意味着证书将不匹配子域。如果连接使用IP地址而不是主机名进行,
则IP地址将与类型为iPAddress
或dNSName
的SAN匹配
(不进行任何DNS查找)。如果没有iPAddress
SAN存在,
也没有匹配的dNSName
SAN存在,则主机IP地址将与通用名称属性匹配。
为了与早期版本的PostgreSQL向后兼容,主机IP地址的验证方式与
RFC 6125中的方式不同。
主机IP地址始终与dNSName
SANs以及iPAddress
SANs匹配,
如果不存在相关的SANs,则可以与通用名称属性匹配。
要允许服务器证书验证,必须将一个或者更多个根证书放置在用户主目录下的~/.postgresql/root.crt
文件中(在Microsoft Windows上该文件名为%APPDATA%\postgresql\root.crt
)。如果需要把服务器发来的证书链链接到存储在客户端的根证书,还应该将中间证书加到该文件中。
如果文件~/.postgresql/root.crl
存在(微软 Windows 上的%APPDATA%\postgresql\root.crl
),证书撤销列表(CRL)项也会被检查。
根证书文件和CRL的位置可以通过设置连接参数sslrootcert
和sslcrl
或环境变量PGSSLROOTCERT
和PGSSLCRL
来更改。
sslcrldir
或环境变量PGSSLCRLDIR
也可以用来指定包含CRL文件的目录。
为了与 PostgreSQL 的早期版本达到向后兼容,如果存在一个根 CA 文件,sslmode
=require
的行为将与verify-ca
相同,即服务器证书根据 CA 验证。我们鼓励依赖这种行为,并且需要证书验证的应用应该总是使用verify-ca
或者verify-full
。
如果服务器尝试通过请求客户端的叶证书来验证客户端的身份,
libpq将发送存储在文件
~/.postgresql/postgresql.crt
中的证书,该文件位于用户的主目录中。
证书必须链到服务器信任的根证书。匹配的
私钥文件~/.postgresql/postgresql.key
也必须存在。
在Microsoft Windows上,这些文件的名称分别为
%APPDATA%\postgresql\postgresql.crt
和
%APPDATA%\postgresql\postgresql.key
。
证书和密钥文件的位置可以通过连接参数
sslcert
和sslkey
,
或通过环境变量PGSSLCERT
和PGSSLKEY
来覆盖。
在Unix系统上,私钥文件的权限必须禁止任何对世界或组的访问;可以通过类似以下命令来实现:
chmod 0600 ~/.postgresql/postgresql.key
。
或者,该文件可以由root拥有并具有组读取权限(即0640
权限)。
该设置适用于由操作系统管理证书和密钥文件的安装。然后,libpq的用户应该成为具有对这些证书和密钥文件访问权限的组的成员。
(在Microsoft Windows上,没有文件权限检查,因为假定%APPDATA%\postgresql
目录是安全的。)
postgresql.crt
中的第一个证书必须是客户端的证书,因为它必须匹配客户端的私钥。可以选择将“中间”证书追加到该文件 — 这样做避免了在服务器上存放中间证书的要求(ssl_ca_file)。
证书和密钥可能是 PEM 或 ASN.1 DER 格式。
密钥可以以明文存储,也可以使用OpenSSL支持的任何算法(例如AES-128)使用密码进行加密。
如果密钥是加密存储的,那么可以在sslpassword连接选项中提供密码。
如果提供了加密密钥,而且sslpassword
选项不存在或为空,如果TTY可用,那么OpenSSL将交互式地以Enter PEM pass phrase:
提示输入密码。
应用程序可以越过客户端证书提示和sslpassword
参数的处理,通过提供它们自己的密钥密码回调;参见PQsetSSLKeyPassHook_OpenSSL
。
创建证书的指令请参考第 19.9.5 节。
sslmode
参数的不同值提供了不同级别的保护。SSL 能够针对三类攻击提供保护:
如果一个第三方能够检查客户端和服务器之间的网络流量,它能读取连接信息(包括用户名和口令)以及被传递的数据。SSL使用加密来阻止这种攻击。
如果一个第三方能对客户端和服务器之间传送的数据进行修改,它就能假装是服务器并且因此能看见并且修改数据,即使这些数据已被加密。然后第三方可以将连接信息和数据转送给原来的服务器,使得它不可能检测到攻击。这样做的通常途径包括 DNS 污染和地址劫持,借此客户端被重定向到一个不同的服务器。还有几种其他的攻击方式能够完成这种攻击。SSL使用证书验证让客户端认证服务器,就可以阻止这种攻击。
如果一个第三方能假装是一个授权的客户端,它能够简单地访问它本不能访问的数据。通常这可以由不安全的口令管理所致。SSL使用客户端证书来确保只有持有合法证书的客户端才能访问服务器,这样就能阻止这种攻击。
对于一个已知的SSL-secured连接,在连接被建立之前,SSL 使用必须被配置在客户端和服务器之上。如果只在服务器上配置,客户端在知道服务器要求高安全性之前可能会结束发送敏感信息(例如口令)。在 libpq 中,要确保连接安全,可以设置sslmode
参数为verify-full
或verify-ca
并且为系统提供一个根证书用来验证。这类似于使用一个https
URL进行加密网页浏览。
一旦服务器已经被认证,客户端可以传递敏感数据。这意味着直到这一点,客户端都不需要知道是否证书将被用于认证,这样只需要在服务器配置中指定就比较安全。
所有SSL选项都带来了加密和密钥交换的负荷,因此必须在性能和安全性之间做出平衡。表 34.1不同sslmode
值所保护的风险,以及它们是怎样看待安全性和负荷的。
表 34.1. SSL 模式描述
sslmode | 窃听保护 | MITM保护 | 声明 |
---|---|---|---|
disable | No | No | 我不关心安全性,并且我不想为加密增加负荷。 |
allow | 可能 | No | 我不关心安全性,但如果服务器坚持,我将承担加密带来的负荷。 |
prefer | 可能 | No | 我不关心安全性,但如果服务器支持,我希望承担加密带来的负荷。 |
require | Yes | No | 我想要对数据加密,并且我接受因此带来的负荷。我信任该网络会保证我总是连接到想要连接的服务器。 |
verify-ca | Yes | Depends on CA policy | 我想要对数据加密,并且我接受因此带来的负荷。我想要确保我连接到的是我信任的服务器。 |
verify-full | Yes | Yes | 我想要对数据加密,并且我接受因此带来的负荷。我想要确保我连接到的是我信任的服务器,并且就是我指定的那一个。 |
verify-ca
和verify-full
之间的区别取决于根CA
的策略。如果使用了一个公共CA
,verify-ca
允许连接到那些可能已经被其他人注册到该CA
的服务器。在这种情况下,总是应该使用verify-full
。如果使用了一个本地CA
或者甚至是一个自签名的证书,使用verify-ca
常常就可以提供足够的保护。
sslmode
的默认值是prefer
。如表中所示,这在安全性的角度来说没有意义,并且它只承诺可能的性能负荷。提供它作为默认值只是为了向后兼容,并且我们不推荐在安全部署中使用它。
表 34.2总结了与客户端 SSL 设置相关的文件。
表 34.2. Libpq/客户端 SSL 文件用法
文件 | 内容 | 效果 |
---|---|---|
~/.postgresql/postgresql.crt | 客户端证书 | 发送到服务器 |
~/.postgresql/postgresql.key | 客户端私钥 | 证明客户端证书是由拥有者发送;不代表证书拥有者可信 |
~/.postgresql/root.crt | 可信的证书机构 | 检查服务器证书是由一个可信的证书机构签发 |
~/.postgresql/root.crl | 被证书机构撤销的证书 | 服务器证书不能在这个列表上 |
如果您的应用程序初始化libssl
和/或libcrypto
库,并且libpq
构建时带有SSL支持,您应该调用PQinitOpenSSL
告诉libpq
libssl
和/或libcrypto
库已被您的应用程序初始化,以便
libpq不会再初始化这些库。
但是,当使用OpenSSL版本1.1.0或更高版本时,重复初始化不再成问题。
PQinitOpenSSL
允许应用选择要初始化哪个安全性库。
void PQinitOpenSSL(int do_ssl, int do_crypto);
当do_ssl
是非零时,libpq将在第一次打开数据库连接前初始化OpenSSL库。
当do_crypto
是非零时,libcrypto
库将被初始化。
默认情况下(如果没有调用PQinitOpenSSL
),两个库都会被初始化。
当 SSL 支持没有被编译时,这个函数也存在但是什么也不做。
如果你的应用使用并且初始化OpenSSL或者它的底层libcrypto
库,你必须在第一次打开数据库连接前以合适的非零参数调用这个函数。
同时要确保在打开一个数据库连接前已经完成了初始化。
PQinitSSL
允许应用选择要初始化哪个安全性库。
void PQinitSSL(int do_ssl);
这个函数等效于PQinitOpenSSL(do_ssl, do_ssl)
。
这对于要么初始化OpenSSL以及libcrypto
要么都不初始化的应用足够用了。
PQinitSSL
从PostgreSQL 8.0 就存在了,
而PQinitOpenSSL
直到PostgreSQL 8.4 才被加入,因此PQinitSSL
可能对那些需要与旧版本libpq一起工作的应用来说更合适。