PostgreSQL 原生支持使用 SSL 连接, 并以 TLS 协议加密客户端/服务器通信, 以提高安全性。 服务器侧 SSL 功能的细节请参见 Section 18.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 地址会被
(不做任何 DNS 查找地)
与类型为
iPAddress 或
dNSName
的 SAN 相匹配。
如果既没有
iPAddress SAN,
也没有匹配的
dNSName SAN,
则主机 IP 地址会与通用名称属性相匹配。
为了与 PostgreSQL 的早期版本向后兼容,
主机 IP 地址的验证方式不同于
RFC 6125。
主机 IP 地址总是与
dNSName SAN
以及 iPAddress SAN
相匹配,
并且在不存在相关 SAN 的情况下,
也可以与通用名称属性相匹配。
为了允许服务器证书验证,
必须把一个或多个根证书放置在用户主目录下的文件
~/.postgresql/root.crt
中。
(在 Microsoft Windows 上,
该文件被命名为
%APPDATA%\postgresql\root.crt。)
如果需要中间证书来把服务器发送的证书链链接到客户端上存储的根
证书,
那么也应当把中间证书添加到该文件中。
如果文件
~/.postgresql/root.crl
存在
(Microsoft 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。
有关创建证书的说明, 请参见 Section 18.9.5。
sslmode
参数的不同值提供不同级别的保护。
SSL 可以针对三类攻击提供保护:
如果第三方能够检查客户端与服务器之间的网络流量, 它就能读取连接信息 (包括用户名和密码) 以及被传递的数据。 SSL 使用加密来防止这一点。
如果第三方能够在客户端与服务器之间传递数据时修改它, 它就可以冒充服务器, 因此即使数据是加密的, 它也能查看并修改数据。 然后第三方可以把连接信息和数据转发给原始服务器, 使这种攻击无法被检测出来。 常见的攻击途径包括 DNS 污染和地址劫持, 即把客户端引导到一台与预期不同的服务器。 还有若干其他能达成这一目的的攻击方法。 SSL 通过向客户端验证服务器来防止这一点, 即使用证书验证。
如果第三方能够冒充一个经过授权的客户端, 它就可以简单地访问它本不应访问的数据。 通常这会通过不安全的密码管理发生。 SSL 使用客户端证书来防止这一点, 确保只有有效证书的持有者才能访问服务器。
要让一条连接被认为是 SSL 安全的,
必须在连接建立之前
同时在客户端和服务器上配置 SSL 的使用。
如果只在服务器上配置了它,
那么客户端可能在知道服务器要求高安全性之前,
就已经发送了敏感信息
(例如密码)。
在 libpq 中,
可以通过把
sslmode
参数设置为
verify-full 或
verify-ca,
并向系统提供一个用于验证的根证书,
来确保连接是安全的。
这类似于在加密的网页浏览中使用一个
https
URL。
一旦服务器通过了身份验证, 客户端就可以传递敏感数据。 这意味着在这之前, 客户端不需要知道证书是否会用于身份验证, 因此仅在服务器配置中指定这一点是安全的。
所有
SSL
选项都以加密和密钥交换的形式带来开销,
因此必须在性能与安全之间做出权衡。
Table 32.1
说明了不同的
sslmode
值能防范哪些风险,
以及它们就安全性和开销做出了怎样的声明。
Table 32.1. SSL 模式说明
sslmode | 窃听保护 | MITM 保护 | 声明 |
|---|---|---|---|
disable | 否 | 否 | 我不在乎安全, 也不想为加密付出开销。 |
allow | 可能 | 否 | 我不在乎安全, 但如果服务器坚持要求, 我会为加密付出开销。 |
prefer | 可能 | 否 | 我不在乎加密, 但如果服务器支持, 我愿意为加密付出开销。 |
require | 是 | 否 | 我想要我的数据被加密, 并且我接受这种开销。 我相信网络会确保我总是连接到我想要的服务器。 |
verify-ca | 是 | 取决于 CA 策略 | 我想要我的数据被加密, 并且我接受这种开销。 我想确保自己连接到一台我信任的服务器。 |
verify-full | 是 | 是 | 我想要我的数据被加密, 并且我接受这种开销。 我想确保自己连接到一台我信任的服务器, 并且它就是我所指定的那台。 |
verify-ca 与
verify-full
之间的区别取决于根
CA
的策略。
如果使用一个公共
CA,
那么
verify-ca
允许连接到
别人可能已向该
CA
注册的一台服务器。
在这种情况下,
应当始终使用
verify-full。
如果使用一个本地
CA,
甚至是一张自签名的证书,
那么使用
verify-ca
通常就能提供足够的保护。
sslmode
的默认值是
prefer。
如表所示,
从安全的角度来看这是没有意义的,
它只承诺在可能的情况下付出性能开销。
提供它作为默认值只是为了向后兼容,
在安全的部署中不推荐使用它。
Table 32.2 总结了与客户端上的 SSL 设置相关的文件。
Table 32.2. libpq 客户端 SSL 文件对照表
| File | Contents | Effect |
|---|---|---|
~/.postgresql/postgresql.crt | client certificate | sent to server |
~/.postgresql/postgresql.key | 客户端私钥 | 证明客户端证书由其持有者发送; 并不表明证书持有者是值得信任的 |
~/.postgresql/root.crt | 受信任的证书颁发机构 | 检查服务器证书是否由一个受信任的证书颁发机构签名 |
~/.postgresql/root.crl | 被证书颁发机构吊销的证书 | 服务器证书不得出现在这个列表上 |
需要与
PostgreSQL
的较旧版本兼容
(使用
OpenSSL
版本 1.0.2 或更早)
的应用程序,
需要在使用 SSL
库之前先初始化它。
初始化了
libssl 和/或
libcrypto
库的应用程序应当调用
PQinitOpenSSL
来告诉
libpq:
libssl 和/或
libcrypto
库已经由你的应用程序初始化过了,
这样
libpq
就不会再初始化那些库。
然而,
在使用
OpenSSL
版本 1.1.0 或更高版本时,
这是不必要的,
因为重复的初始化已不再是问题。
关于它们的使用的细节, 请参见你所针对的 PostgreSQL 版本的文档。
PQinitOpenSSL #允许应用程序选择要初始化哪些安全库。
void PQinitOpenSSL(int do_ssl, int do_crypto);
这个函数已被弃用, 它的存在只是为了向后兼容, 它什么都不做。
PQinitSSL #允许应用程序选择要初始化哪些安全库。
void PQinitSSL(int do_ssl);
这个函数等同于
PQinitOpenSSL(do_ssl, do_ssl)。
这个函数已被弃用,
它的存在只是为了向后兼容,
它什么都不做。
PQinitSSL 和
PQinitOpenSSL
为向后兼容而保留,
但自
PostgreSQL 18
起已不再需要。
PQinitSSL
从
PostgreSQL 8.0
就已存在,
而
PQinitOpenSSL
是在
PostgreSQL 8.4
中加入的,
因此对于需要与较旧版本的
libpq
一起工作的应用程序来说,
PQinitSSL
可能是更好的选择。