PostgreSQL 原生支持使用SSL连接,以使用TLS协议加密客户端/服务器通信,从而提高安全性。有关服务器端功能的详细信息,请参阅第 18.9 节。SSL功能。
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
SAN 以及 iPAddress
SAN 进行匹配,如果不存在相关的 SAN,则可以与“通用名称”属性匹配。
要允许服务器证书验证,必须将一个或多个根证书放置在用户主目录中的文件 ~/.postgresql/root.crt
中。(在 Microsoft Windows 上,该文件名为 %APPDATA%\postgresql\root.crt
。)如果需要将服务器发送的证书链链接到客户端上存储的根证书,则还应将中间证书添加到该文件中。
如果文件 ~/.postgresql/root.crl
存在(Microsoft Windows 上为 %APPDATA%\postgresql\root.crl
),则还会检查证书吊销列表 (CRL) 条目。
可以通过设置连接参数 sslrootcert
和 sslcrl
或环境变量 PGSSLROOTCERT
和 PGSSLCRL
来更改根证书文件和 CRL 的位置。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
选项不存在或为空,OpenSSL 将在 TTY 可用时交互式地提示输入密码,提示符为 Enter PEM pass phrase:
。应用程序可以通过提供自己的密钥密码回调来覆盖客户端证书提示和 sslpassword
参数的处理;请参阅 PQsetSSLKeyPassHook_OpenSSL
。
有关创建证书的说明,请参阅第 18.9.5 节。
sslmode
参数的不同值提供了不同级别的保护。SSL 可以提供针对三种类型攻击的保护:
如果第三方可以检查客户端和服务器之间的网络流量,则它可以读取连接信息(包括用户名和密码)以及传递的数据。SSL使用加密来防止这种情况。
如果第三方可以在客户端和服务器之间传递数据时修改数据,则它可以伪装成服务器,因此可以查看和修改数据,即使数据已加密。然后,第三方可以将连接信息和数据转发到原始服务器,从而使这种攻击无法被检测到。实现此目的的常见方式包括 DNS 投毒和地址劫持,从而将客户端定向到与预期不同的服务器。还有其他几种攻击方法可以实现此目的。SSL使用证书验证来防止这种情况,方法是向客户端验证服务器的身份。
如果第三方可以伪装成授权客户端,则它可以简单地访问它不应该有权访问的数据。通常,这可以通过不安全的密码管理发生。SSL使用客户端证书来防止这种情况,方法是确保只有持有有效证书的人员才能访问服务器。
要使连接被认为是 SSL 安全的,必须在客户端和服务器都配置 SSL 用法,然后才能建立连接。如果仅在服务器上配置了 SSL,则客户端可能最终在知道服务器需要高安全性之前发送敏感信息(例如,密码)。在 libpq 中,可以通过将 sslmode
参数设置为 verify-full
或 verify-ca
并向系统提供根证书进行验证来确保安全连接。这类似于使用 https
URL进行加密的 Web 浏览。
一旦服务器经过身份验证,客户端就可以传递敏感数据。这意味着直到此时,客户端不需要知道是否将使用证书进行身份验证,因此可以安全地仅在服务器配置中指定它。
所有SSL选项都会产生加密和密钥交换形式的开销,因此需要在性能和安全性之间进行权衡。表 32.1 说明了不同的 sslmode
值所防范的风险,以及它们关于安全性和开销的说明。
表 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
。如表中所示,从安全的角度来看,这是没有意义的,并且它仅在可能的情况下承诺性能开销。仅将其作为默认值提供是为了向后兼容,不建议在安全部署中使用。
表 32.2 总结了与客户端 SSL 设置相关的文件。
表 32.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 中添加,因此对于需要使用较旧版本 libpq 的应用程序来说,PQinitSSL
可能是更可取的。
如果您在文档中看到任何不正确、与您特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。