32.18. SSL 支持
PostgreSQL本地支持使用SSL 连接加密客户端/服务器通信以提高安全性。关于服务器端的SSL 功能详见第 18.9 节。
libpq读取系统范围的OpenSSL 配置文件。默认情况下,这个文件被命名为openssl.cnf并且存放在 openssl version -d报告的目录中。可以通过设置环境变量 OPENSSL_CONF把这个默认值覆盖为想要的配置文件的名称。
32.18.1. 服务器证书的客户端验证
默认情况下,PostgreSQL将不会执行服务器证书的任何验证。 这意味着可以在不被客户端知晓的情况下伪造服务器身份 (例如通过修改一个 DNS 记录或者接管服务器的 IP 地址)。为了阻止哄骗, 必须使用SSL证书验证。
如果参数sslmode被设置为verify-ca, libpq 将通过检查证书链一直到一个可信的证书机构(CA) 来验证服务器是可信的。如果sslmode被设置为verify-full, libpq 将还会验证服务器主机名是否匹配它的证书。 如果服务器证书不能被验证,SSL 连接将失败。在大部分对安全敏感的环境中, 建议使用verify-full。
在verify-full模式下,主机名与证书的Subject Alternative Name (主题备用名称)属性进行匹配, 或者如果没有类型为dNSName的主题备用名称,则与Common Name(公用名称)属性进行匹配。 如果证书的名称属性以星号( * )开头,则星号将被视为通配符, 其将匹配除了点(.)之外的所有字符。这意味着证书将不会匹配子域。 如果使用IP地址而不是主机名进行连接,则将匹配IP地址(不进行任何DNS查找)。
要允许服务器证书验证,一个或多个可信的CA必须被放置在用户home目录下的文件 ~/.postgresql/root.crt中。如果中间CA出现在 root.crt中,该文件必须也包含到它们的根CA的证书链 (在微软 Windows 上该文件被命名为%APPDATA%\postgresql\root.crt)。
如果文件~/.postgresql/root.crl存在 (微软 Windows 上的%APPDATA%\postgresql\root.crl), 也会检查证书撤销列表(CRL)项。
根证书文件和 CRL 的位置可以通过设置连接参数sslrootcert和 sslcrl或环境变量PGSSLROOTCERT和PGSSLCRL改变。
注意: 为了与 PostgreSQL 的早期版本达到向后兼容,如果存在一个根 CA 文件, sslmode=require的行为将与 verify-ca相同,意味着服务器证书根据 CA 验证。 不鼓励依赖这种行为,并且需要证书验证的应用程序应该总是使用 verify-ca或者verify-full。
32.18.2. 客户端证书
如果服务器要求一个可信的客户端证书,libpq 将发送用户主目录中~/.postgresql/postgresql.crt文件存储的证书。 该证书必须由一个受服务器信任的证书机构(CA)签发。 也必须存在一个匹配的私钥文件~/.postgresql/postgresql.key。 该私钥文件不允许全局或组用户的任何访问,可以通过命令 chmod 0600 ~/.postgresql/postgresql.key实现。 在微软 Windows 上这些文件被命名为%APPDATA%\postgresql\postgresql.crt 和%APPDATA%\postgresql\postgresql.key,不会有特别的权限检查, 因为该目录被假定为安全。证书和密钥文件的位置可以使用连接参数sslcert 和sslkey或者环境变量PGSSLCERT和PGSSLKEY覆盖。
在一些情况下,客户端证书可以由"中间"证书机构签名, 而不是由服务器直接信任的证书机构。 要使用这样一个证书,将签发机构的证书加入到postgresql.crt文件, 然后是它的上级机构的证书,并且一直到一个受服务器信任的证书机构( "根"机构或者"中间"机构),即由该服务器的 root.crt文件中的一个证书签发。
注意客户端的~/.postgresql/root.crt 列出了被认为可信的能用于签发服务器证书的顶层 CA。 原则上不需要列出签发客户端证书的 CA, 大部分情况下这些 CA 也被信任可以用于服务器证书。
32.18.3. 不同模式中提供的保护
sslmode参数的不同值提供了不同级别的保护。 SSL 能够针对三类攻击提供保护:
- 窃听
如果一个第三方能够检查客户端和服务器之间的网络流量, 它能读取连接信息(包括用户名和口令)以及被传递的数据。 SSL使用加密来阻止这种攻击。
- 中间人(MITM)
如果一个第三方能对客户端和服务器之间传送的数据进行修改, 它就能假装是服务器并且因此能看见并且修改数据,即使这些数据已被加密。 然后第三方可以将连接信息和数据传送给原来的服务器,使得它不可能检测到攻击。 这样做的常用载体包括 DNS 中毒和地址劫持,借此客户端被定向到预期之外的不同的服务器。 还有几种其他的攻击方式能够完成这种攻击。SSL 使用证书验证让客户端认证服务器,就可以阻止这种攻击。
- 模仿
如果第三方可以伪装成一个授权的客户端, 那么它能够轻松访问它本不能访问的数据。通常这可以由不安全的口令管理所致。 SSL使用客户端证书来阻止这种情况, 即确保只有持有合法证书的客户才能访问服务器。
对于一个已知安全的连接,在连接被建立之前,必须在 客户端和服务器端都进行SSL配置。如果只在服务器上配置, 客户端在知道服务器要求高安全性之前可能会结束发送敏感信息(例如口令)。 在 libpq 中,可以通过将sslmode参数设置为verify-full或 verify-ca来确保安全连接,并且为系统提供一个根证书用来验证。 这类似于使用Https URL进行加密网页浏览。
一旦服务器已经被认证,客户端可以传递敏感数据。这意味着直到这一点, 客户端都不需要知道是否证书将被用于认证,这样只需要在服务器配置中指定就比较安全。
所有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.18.4. SSL 客户端文件使用
表 32-2总结了与客户端 SSL 设置相关的文件。
表 32-2. Libpq/客户端 SSL 文件用法
文件 | 内容 | 影响 |
---|---|---|
~/.postgresql/postgresql.crt | 客户端证书 | 由服务器要求 |
~/.postgresql/postgresql.key | 客户端私钥 | 证明由所有者发送客户端证书,并不表示证书所有者是可信的 |
~/.postgresql/root.crt | 受信任的证书颁发机构 | 检查服务器证书是由一个可信的证书机构签发 |
~/.postgresql/root.crl | 被证书颁发机构撤销的证书 | 服务器证书必须不在这个列表中 |
32.18.5. SSL 库初始化
如果你的应用初始化libssl或libcrypto库以及
libpq编译为支持SSL,你应该调用
PQinitOpenSSL
来告诉libpq:libssl
或libcrypto库已经被你的应用初始化,这样libpq
将不会再初始化这些库。
关于 SSL API 详见http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04.HTML。
-
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一起工作的应用来说更合适。