
等保测评里最烦人的一类整改项就是数据库“裸奔”。很多单位的 PostgreSQL 装完默认配置直接上线5432 端口对外提供明文服务。测评师拿抓包工具看一下SQL 语句、用户名、口令全在网络上裸奔这一项基本必扣分。其实解决思路不复杂给 PostgreSQL 配上 SSL 加密链路让通信数据不再明文传输。这篇文章就把我从证书生成、服务端开启、客户端连接验证到等保检查项对照的完整过程写出来顺便把踩过的坑一并交代清楚。先说清楚这套东西能解决什么问题。核心就三点第一满足等保测评里“通信传输加密”的要求第二防止数据库口令和业务数据在网络上被监听窃取第三搞清楚 PostgreSQL 的 SSL 机制往后自己处理证书过期、迁移、高可用环境里的加密问题都不慌。适合正在做等保整改的 DBA、运维同学也适合公司安全基线要求强制开启数据库加密但不知从何下手的开发。1. 为什么要给 PostgreSQL 加 SSL不加密的代价和等保要求先说一个我在实际巡检中遇到的场景。有一次帮客户做安全自查业务方坚持说数据库部署在内网不会有人来嗅探。我用 tcpdump 在数据库服务器上抓了几秒钟的包当时就抓到了 psql 客户端执行的 SELECT 语句连密码包的 hash 都在里面。这种明文流量在内网里只要有一个环节被突破数据基本上就全暴露了。所以不要觉得“内网就安全”等保测评里对网络通信加密的要求不管内网外网都不会豁免。等保 2.0 的三级系统在“安全计算环境”和“安全通信网络”里的转身点都明确要求采用密码技术保证通信过程中数据的保密性和完整性。落到 PostgreSQL 上最直接的做法就是启用 SSL/TLS 加密的连接。等保测评师不会只看你端口有没有开他们现场会检查数据库配置文件中是否开启 SSL会登录数据库看连接会话是否用的 SSL甚至直接抓包比对是不是密文。这一连串操作没有提前做好功课现场很容易翻车。很多人还有一个认知误区觉得配了 SSL 就万事大吉认证方式用的还是老掉牙的 md5。PostgreSQL 里 md5 口令用的挑战响应机制虽然不像明文密码那样裸奔但在安全要求严格的场景下并不被认可。等保整改的时候我一般建议顺手把 password_encryption 切到 scram-sha-256配合 SSL 加密链路这样密码存储和传输两个环节都达标。后面讲 pg_hba.conf 的时候会把这个一起带进去。2. 证书准备自签名证书与 CA 证书怎么选openssl 生成实操2.1 自签名够不够用PostgreSQL 服务端启用 SSL必须有一张服务器证书和对应的私钥。证书从哪来两种路子自己用 openssl 生成自签名证书或者找公司内部的 CA 系统签发。等保测评对证书的来源和签发机构没有强制要求关键在于传输链路是否加密。所以自签名证书完全可以应付测评绝大多数中小公司也是这么干的。内部有 CA 系统的建议走正规签发流程把 server.csr 提交给 CA 签发证书下载回来后放进 PGDATA 目录。好处是客户端可以配置 verify-full 完成证书校验安全强度更高。没有 CA 的也不用纠结自签名配合合理的密钥长度和有效期管理同样能满足合规要求。下面我以自签名为例讲具体命令CA 签发的同学只需要跳过生成证书那一步把 CA 给你的 crt 文件放进来就行。2.2 生成私钥和自签名证书证书的生成直接放在 PostgreSQL 数据目录里做免得后面还要处理文件拷贝和权限归属。假设数据目录是/var/lib/pgsql/15/data执行cd /var/lib/pgsql/15/data openssl req -new -x509 -days 365 -nodes \ -text \ -out server.crt \ -keyout server.key \ -subj /CNpgserver.example.com-nodes的意思是私钥不加密。服务端启动时要用私钥完成 SSL 握手私钥如果设了密码数据库启动的时候没人去给你交互输入密码所以服务端私钥必须不带密码。-days 365是有效期天数自签名证书一般一年就够了。-subj里的 CN 建议写成服务器的域名或者客户端连接时使用的 host 名后面客户端用 verify-full 校验的时候会用上。这里有个现代客户端的坑要提前说。很多新版本 PostgreSQL 客户端和中间件在校验证书时不仅看 CN还会检查 Subject Alternative NameSAN。如果证书里没有 SAN 字段psql 直接连可能就报“证书校验失败”。所以更稳妥的做法是生成证书时通过扩展配置文件加入 SANopenssl req -new -text -nodes -out server.csr -keyout server.key \ -subj /CNpgserver.example.com cat san.cnf EOF subjectAltNameDNS:pgserver.example.com,IP:192.168.1.10 EOF openssl x509 -req -in server.csr -signkey server.key \ -days 365 -out server.crt -extfile san.cnf这里先创建一个带 SAN 请求再用 x509 命令自签。你也许还会看到很多老教程直接一条命令搞定证书那些教程通常只是演示功能不一定会触发现代客户端的 SAN 校验。我实际配置时统一走 SAN 方案避免后续因为证书格式问题排查半天。2.3 证书文件权限与位置证书生成好之后有两个点必须检查。第一个是 owner 和权限server.key 必须只能让 postgres 系统用户读写权限设置为 600server.crt 设置为 644 即可。如果权限过松比如 key 文件是 644PostgreSQL 启动时会直接拒绝加载日志里报错类似FATAL: private key file /var/lib/pgsql/15/data/server.key has group or world access第二个点是文件位置。ssl_cert_file和ssl_key_file的默认值就是server.crt和server.key放在数据目录下即可。常见发行版 PostgreSQL 还会提供独立的证书目录但不建议在基础配置阶段搞特殊化用默认位置最省事。此外如果使用客户端证书认证还需要准备root.crt用于验证客户端证书这个后面单讲。3. 服务端开启 SSLpostgresql.conf 与 pg_hba.conf 的配合3.1 postgresql.conf 关键参数PostgreSQL 的 SSL 开关集中在 postgresql.conf 里我常用的参数整理成一张表参数名作用建议值ssl是否启用 SSLonssl_cert_file服务器证书路径默认 server.crtssl_key_file服务器私钥路径默认 server.keyssl_ca_fileCA 根证书用于客户端证书验证按需配置ssl_min_protocol_version允许的最低 TLS 版本TLSv1.2ssl_ciphers可用的加密套件HIGH:!aNULL:!MD5打开 postgresql.conf找到 SSL 部分修改如下ssl on ssl_cert_file server.crt ssl_key_file server.key ssl_min_protocol_version TLSv1.2 ssl_ciphers HIGH:!aNULL:!MD5这里特别提醒一下ssl_min_protocol_version。等保测评中经常检查是否支持 SSLv3、TLSv1.0、TLSv1.1 这些老协议这些协议都存在已知漏洞。PostgreSQL 12 以上可以用这个参数直接锁死最低版本老版本没这个参数只能通过编译选项或者系统 OpenSSL 策略限制。整改时优先升级到受支持的新版本其次才是配置项兜底。修改完配置后需要重载或者重启让参数生效。ssl、ssl_cert_file 这些参数属于 postmaster 级别的参数可以 reload 生效但涉及监听参数变化时重启更稳妥。我的习惯是 reload 后再通过日志和连接测试确认悄悄说一句生产环境重启数据库之前最好把 pg_hba.conf 的改动也一并检查了免得 reload 后连不上自己把自己锁在外面。3.2 pg_hba.conf 强制 SSL 规则postgresql.conf 里 sslon 只是让数据库“支持”SSL不代表所有连接都必须走 SSL。客户端完全可以选择非 SSL 方式连接。真正实现“强制加密”靠的是 pg_hba.conf 里的连接规则。pg_hba.conf 支持三种连接类型local、host、hostssl。host 表示允许明文或 SSLhostssl 则强制使用 SSL如果客户端不走 SSL 直接拒绝。等保要求的是加密传输所以对远端连接必须用 hostssl# TYPE DATABASE USER ADDRESS METHOD hostssl all all 0.0.0.0/0 scram-sha-256 hostssl all all ::0/0 scram-sha-256这里有几个容易踩的点。第一条如果你把原来的 host 规则改成 hostssl要确认客户端连接串里都带了 SSL 参数否则应用会报“no pg_hba.conf entry for host ... SSL off”之类的错误。第二条METHOD 建议用 scram-sha-256 而不是 md5配合 SSL 链路之后安全性才算完整。第三条如果本地还有 unix socket 连接保留 local 规则让本机运维不受影响local all all peerpeer认证对本地 unix socket 使用操作系统的用户名做校验本地管理员通过 postgres 用户 psql 进去不用输密码运维上非常方便。但这条规则不要开放给 TCP 连接更不要把 peer 用在 hostssl 上否则所有远程连接都会因为找不到对应的系统用户而失败。3.3 生效与日志确认配置完成后的标准动作是pg_ctl reload -D /var/lib/pgsql/15/data # 或者用 psql 执行 SELECT pg_reload_conf();确认是否真的生效最直接的方法是看日志和系统视图。PostgreSQL 启动日志中如果打印了类似LOG: SSL is enabled或者LOG: database system was interrupted这类信息不一定是 SSL 开启的证明。更准确的做法是在 psql 里执行SHOW ssl; SHOW ssl_min_protocol_version; SHOW ssl_ciphers;如果 ssl 显示 on说明服务端已经启用。但是“服务端支持 SSL”和“客户端连接强制 SSL”是两回事还得配合下一步的客户端验证才能确定整条链路真的加密。4. 客户端连接验证与配置从 psql 到 JDBC 和 Python4.1 psql 命令行验证连接服务端配置好之后马上用 psql 做一次带 SSL 的连接验证。命令行指定 sslmoderequirepsql host192.168.1.10 port5432 dbnamepostgres userpostgres sslmoderequire这个连接串里sslmoderequire 告诉客户端必须使用 SSL。如果服务端 SSL 配置有问题这个命令会直接报错。连接成功后执行SELECT pid, usename, ssl, version, cipher, client_addr FROM pg_stat_ssl JOIN pg_stat_activity USING (pid);输出的 ssl 列如果是 tcipher 列有具体的加密套件名称比如TLS_AES_256_GCM_SHA384说明当前会话确实在走加密链路。这条 SQL 是测评现场最常看的也是我每次配完必跑一遍的确认无误后才算真正收工。顺便说一个测试小技巧。抓包验证不一定要上 Wireshark。在数据库服务器上抓 5432 端口然后开一个 psql SSL 连接随便执行一条 SELECTtcpdump 输出的数据里如果已经看不到 SQL 明文基本可以说明加密生效tcpdump -i any port 5432 -A -s 0不过现在的 TLS 加密流量在抓包工具里本身就是一堆乱码更直观的判断还是靠 pg_stat_ssl。4.2 sslmode 各模式区别客户端连接时sslmode 参数决定了对 SSL 的要求强度。这个参数的取值容易搞混我理过一次之后就再也没忘过sslmode行为典型使用场景disable不使用 SSL纯内网调试allow先试非 SSL不行再用 SSL兼容老服务prefer先试 SSL不行退到非 SSL默认值兼容性优先require必须 SSL但不验证证书等保最低要求verify-ca必须 SSL验证服务器证书由可信 CA 签发中等安全要求verify-full必须 SSL验证证书且校验主机名与证书 CN/SAN 匹配防中间人攻击等保整改场景我建议配置成 verify-full 或者至少 verify-ca。如果只是自签名证书verify-ca 需要客户端持有 CA 根证书验证逻辑比较好配置。verify-full 要求 CN 或 SAN 与连接 host 一致客户端和服务端两边都要配合好域名解析。对于内部系统通常使用内网域名配置 DNS 或者客户端 hosts 都行这部分提前规划好不要等测评的时候才想起来改连接串。4.3 Java JDBC 与 Python 连接配置实际业务系统中PostgreSQL 基本不会只被 psql 使用Java、Python、.NET 各种客户端都有。每个客户端的 SSL 配置方式不同我列三个最常遇到的。Java JDBC 连接串jdbc:postgresql://pgserver.example.com:5432/mydb?ssltruesslmoderequiresslfactoryorg.postgresql.ssl.NonValidatingFactory如果用了自签名证书且不想把证书导入 Java 的 truststore可以指定NonValidatingFactory。但等保严格的环境下这是不推荐的属于放弃证书校验的妥协手段。更规范的做法是把服务端证书或自签 CA 证书导入 Java 的 cacertskeytool -import -alias pgserver -keystore $JAVA_HOME/lib/security/cacerts -file server.crtPython 的 psycopg2 连接参数import psycopg2 conn psycopg2.connect( hostpgserver.example.com, port5432, dbnamemydb, userappuser, passwordxxxx, sslmoderequire )如果需要验证服务端证书加上sslrootcert参数指向 CA 根证书文件并调整 sslmode 为 verify-caconn psycopg2.connect( hostpgserver.example.com, port5432, dbnamemydb, userappuser, passwordxxxx, sslmodeverify-full, sslrootcert/etc/ssl/certs/pg-ca.crt )psycopg2 在 sslmodeverify-full 模式下会自动校验主机名这一点比很多客户端做得都严格。如果配置时主机名不匹配会报“server certificate for ... does not match host name”这类报错在等保测评的复测中经常出现多半就是证书 CN 跟实际访问域名不一致。4.4 客户端证书认证双向 TLS简介部分等保要求严格的系统还会要求做双向 TLS也就是客户端也要提供服务端签发的证书。这在 PostgreSQL 中通过 pg_hba.conf 的 clientcert 选项实现hostssl all all 0.0.0.0/0 cert clientcertverify-ca服务端需要设置ssl_ca_file指向签发客户端证书的 CA 根证书客户端连接时带上自己的证书和私钥。psql 连接命令类似psql hostpgserver.example.com dbnamemydb userappuser sslmodeverify-full sslcertclient.crt sslkeyclient.key sslrootcertca.crt双向 TLS 的优点是安全性更高缺点是需要维护客户端证书的分发和撤销。绝大多数业务系统用单向 SSL 加用户名密码认证已经能满足等保要求双向 TLS 在党政机关和金融机构里更常见。这块我建议先评估业务压力别一股脑上证书一多运维成本会明显上升。5. 常见报错与排查心得我踩过的坑都在这了5.1 明明开了 SSLpg_stat_ssl 查不到会话这是最容易踩的坑。postgresql.conf 里 sslon 配好了psql 也能正常连接但 pg_stat_ssl 里查出来一堆连接的 ssl 列是 f。原因基本都是 pg_hba.conf 里没有配置 hostssl或者客户端连接时 sslmode 用的是默认 prefer。prefer 模式会优先尝试 SSL如果 SSL 不可用或者没配好客户端会自动退回到明文连接很容易造成“我以为加密了其实没有”的假象。排查思路先确认 pg_hba.conf 里对目标地址的规则类型是 hostssl再确认客户端连接串 sslmode 不是 prefer。这里强烈建议把应用连接串的 sslmode 显式设置为 require 或更高级别不要依赖数据库侧的单方面配置。5.2 私钥权限不对导致启动失败新生成的 server.key 默认权限可能是 644PostgreSQL 出于安全考虑会对私钥文件做权限检查。日志中如果出现FATAL: private key file /var/lib/pgsql/15/data/server.key has group or world access处理方法很直接chown postgres:postgres server.key chmod 600 server.key这个报错还有一个变种就是证书和私钥不匹配报错信息是FATAL: SSL error: certificate verify failed一般是因为证书和私钥不是一对。我之前就干过把两台服务器的证书文件搞混的事查了半天才意识到是拷贝文件的时候张冠李戴。匹配验证可以用 openssl 手动比对证书公钥和私钥的 modulus 是否一致openssl x509 -in server.crt -noout -modulus | md5sum openssl rsa -in server.key -noout -modulus | md5sum两个命令输出的 md5 值一致才说明证书和私钥是一对。5.3 “no required ssl certificate was sent”这个报错主要出现在配置了客户端证书认证的场景。pg_hba.conf 里写了 clientcertverify-full 或者 verify-ca但客户端连接时没有提供证书或者提供的证书不被服务端信任PostgreSQL 就会拒绝连接并提示FATAL: connection requires a valid client certificate FATAL: no pg_hba.conf entry for host 192.168.1.20, user appuser, database mydb, no encryption处理办法是确认客户端连接串里确实带了 sslcert 和 sslkey 参数同时确认 ssl_ca_file 里配置的 CA 能验证客户端证书链。很多系统配了双向 SSL只改了服务端客户端连接串更新不及时导致半天连不上这种问题应用方最容易忽略。5.4 连接串需要换行转义PostgreSQL 的连接串看起来简单实际上参数一多就容易出问题。尤其是 sslcert、sslkey、sslrootcert 这些路径在配置文件里如果路径带空格或者连接串经过 shell 或者应用配置文件的转义参数会莫名被截断。我的习惯是路径尽量不包含空格连接串统一用单引号包裹避免 shell 展开导致参数丢失。还有一个版本差异的问题要注意。老版本的 libpq 不支持 ssl_min_protocol_version有些老客户端连过来会协商到 TLSv1.0导致等保测评时“使用的加密算法不安全”。遇到这个问题最省事的办法是客户端升级到新版本。如果客户端版本实在没法动再去考虑通过改 openssl 全局配置强制压低协议版本但这样影响面大操作前一定先评估。6. 等保测评检查项对照与日常运维建议6.1 测评师现场会查什么把测评现场常见的检查点和对应的整改措施整理成表方便对照自查检查点测评方法整改要求数据库通信是否加密查看配置文件、查询 pg_stat_sslpostgresql.conf 开启 ssl远程连接强制 hostssl是否使用不安全协议使用脚本或扫描工具检测 TLS 版本ssl_min_protocol_version 设为 TLSv1.2 以上密码是否加密传输查看 pg_hba.conf 认证方式使用 scram-sha-256 替代 md5证书私钥是否安全检查文件权限server.key 权限 600属主 postgres证书是否过期查看证书有效期证书有效期需在有效范围内建议自动续期是否进行了抓包验证测评现场可能实时抓包确保连接流量为密文测评师看过配置后通常还会在测评现场实际发起一次连接查看会话的加密状态。只要 pg_stat_ssl 视图里显示 sslt而且 pg_hba.conf 中远端规则全是 hostssl这一项基本就过了。6.2 证书过期与自动续期的运维方案自签名证书有效期一年明年的今天如果忘了续期数据库服务端 SSL 功能会直接失效。为了避免这种尴尬我在实践中做了两件事。第一用脚本监控证书有效期把过期前 30 天、7 天的告警接入现有的监控体系。最简单的脚本逻辑expire_date$(openssl x509 -in /var/lib/pgsql/15/data/server.crt -noout -enddate | cut -d -f2) expire_epoch$(date -d $expire_date %s) now_epoch$(date %s) days_left$(( (expire_epoch - now_epoch) / 86400 )) if [ $days_left -lt 30 ]; then echo PostgreSQL server.crt will expire in $days_left days fi第二证书更新后需要重载 PostgreSQL而不是重启。更新证书文件后执行SELECT pg_reload_conf();即可让新证书生效停机时间可以做到零。需要注意的是证书更新后旧的客户端长连接不会自动断开新连接才会使用新证书。如果测评当天发现证书过期重签证书后 reload 就能快速恢复。6.3 高可用和主从环境的 SSL 注意事项如果 PostgreSQL 做了主从复制证书配置需要特别注意两点。一是主库和从库的证书建议使用同一个 CA 签发但 CN 可以各自独立。在流复制配置中从库通过primary_conninfo连接主库时同样需要指定 sslmode如果不指定默认 prefer 可能会退化为明文复制这同样会被等保判定为不合规。在 primary_conninfo 里显式加上primary_conninfo hostpgprimary.example.com port5432 userreplicator passwordxxx sslmoderequire二是证书更新时要主从一起做。只更新主库证书从库还在用旧证书一旦发生主从切换新主库拿旧证书对外服务客户端校验就会失败。建议把证书更新做成标准运维作业主从节点全部跑完按一个检查单确认所有节点的 pg_stat_ssl 都正常再收工。一些实操心得最后说一个我个人的体会。PostgreSQL 配 SSL技术上真不难难的是一开始就规划好证书生命周期和客户端连接规则。我见过太多客户到了测评前一周才临时配证书自签名证书 CN 随便填应用连接串 sslmode 不指定结果测评师一查掉链子。建议拿到整改需求后先把证书的签发方式、有效期、客户端兼容性这些基础问题想清楚再动手改配置。再分享一个小技巧每次改完 SSL 相关配置我都会做一次“断网式”验证就是把局域网其他机器访问数据库的权限临时收紧再从应用侧发起一次完整请求看报错日志里有没有 SSL 相关告警。这一步看起来多余但能提前发现很多应用连接串配置遗漏的问题比测评当天被查出来强得多。等保整改这件事技术是手段平时的运维习惯才是真正兜底的东西。