PostgreSQL SSL 配置:从"看起来加密"到"真正加密"

2026-05-11 24 预计阅读时间:1 分钟
来源:postgr.es AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:11 分钟

你的 PostgreSQL 开启了 SSL,日志里也没有报错——但你的查询和密码可能仍在网络上明文传输。这不是夸张,而是大多数部署的真实状态。默认的 host 条目允许非加密连接静默通过,客户端默认的 sslmode=prefer 会无声地降级回明文。配置文件写了 ssl = on,不代表连接真的走了加密隧道。

把这件事做对,需要一条完整的链路:证书 → 服务端配置 → 强制拒绝明文 → 客户端验证 → 持续监控。缺任何一环,加密就是摆设。

明文连接暴露了什么

PostgreSQL 默认不加密网络传输。用户名、密码、每一条 SQL、每一行返回数据——全部裸奔在 TCP 上。一个同网段的嗅探器就够了,不需要任何高级攻击技术。

"我们在内网/VPC 里,不需要加密"是最常见的误判。内网不能防御横向移动:攻击者一旦进入边界内部,明文数据库连接就是最肥的猎物。

SSL/TLS 在数据交换之前建立加密隧道。传输内容变成只有持有会话密钥的双方才能解读的噪声。PostgreSQL 文档和社区习惯仍称 SSL,实际指现代 TLS——别被名字迷惑。

加密隧道的建立过程

客户端发起 SSL 连接时,数据库流量交换之前,以下步骤先完成:

  1. 客户端打开 TCP 连接,请求 SSL。
  2. 服务端发送证书——包含公钥和身份签名的文件。
  3. 客户端验证:证书是否由受信 CA 签发?证书中的主机名是否与连接目标匹配?
  4. 双方协商加密套件,推导共享会话密钥。
  5. 之后所有流量——认证、查询、结果——都在隧道内传输。

第 3 步是关键。不验证证书,你可能把加密数据直接送到了攻击者的机器上。证书是"你在和正确的服务器对话"的证明。

生成证书:先造钥匙再开门

PostgreSQL 不自带证书生成能力,用 OpenSSL。关键顺序:证书文件必须在编辑 postgresql.conf 之前就位。ssl = on 生效时,PostgreSQL 立刻查找证书和密钥文件,找不到则拒绝启动。先造钥匙,再开门。

开发和内部工具用自签名证书完全够用——你控制两端,不需要第三方背书:

# Step 1 — 生成私钥
openssl genrsa -out server.key 4096

# Step 2 — 生成自签名证书,有效期 365 天
# CN 必须匹配客户端连接时使用的主机名
openssl req -new -x509 -days 365 -key server.key -out server.crt \
  -subj "/CN=db-hostname"

# Step 3 — 自签名证书本身就是 CA
# 复制为 root.crt,供客户端验证使用
cp server.crt root.crt

# Step 4 — 锁定权限(PostgreSQL 权限不对会拒绝启动)
chmod 0600 server.key
chown postgres:postgres server.key server.crt root.crt

server.keyserver.crtroot.crt 三个文件放入 PostgreSQL 数据目录。不确定路径?执行:

psql -c "SHOW data_directory;"

为什么把 server.crt 复制为 root.crt?客户端 sslmode=verify-full 需要一个 CA 证书来验证服务端。自签名证书的签发者就是自己,所以同一个文件既当服务端证书又当客户端信任根。

生产环境应使用正规 CA 签发的证书:生成私钥和 CSR,提交给内部或公共 CA,拿回签发证书作为 server.crt。这样客户端无需持有服务端证书本身,只需信任 CA 根。

postgresql.conf:开启加密通道

证书就位后,编辑 postgresql.conf

# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'     # 相对于数据目录
ssl_key_file = 'server.key'
ssl_ca_file = 'root.crt'         # 客户端 verify-full 必需
ssl_min_protocol_version = 'TLSv1.2'  # 拒绝不安全的老版本协议

ssl = on 让加密连接可用,但不强制。强制是 pg_hba.conf 的事。

ssl_ca_file 经常被示例遗漏,但客户端要 verify-full 就必须有它。没有 CA 文件,服务端无法配合完成完整验证链。

一个词决定安全还是摆设

pg_hba.conf 的连接类型列有三个关键值:

行为 适用场景
host 同时接受 SSL 和明文 仅本地开发
hostssl 只接受 SSL,明文直接拒绝 生产环境
hostnossl 只接受明文,拒绝 SSL 极少特殊场景

大多数部署默认用 host。不请求 SSL 的客户端照样进来,日志里记录的是"连接成功",没有任何标记说明它未加密。一切看起来正常。

host 改成 hostssl,是关闭缺口的那一步——没有降级,没有回退,没有选项:

# pg_hba.conf
# TYPE  DATABASE  USER  ADDRESS       METHOD
hostssl all       all   0.0.0.0/0     scram-sha-256

改完两个配置文件后,重启 PostgreSQL,不是 reload。ssl = on 需要完整重启才生效。

客户端 sslmode:六个级别,只有两个安全

客户端通过连接字符串的 sslmode 控制行为。六个模式中,大多数给了"安全感"而非真实安全:

模式 行为 安全?
disable 不用 SSL
prefer 尝试 SSL,失败则静默降级明文 否——这是 libpq 和多数 ORM 的默认值
require 要求 SSL,但不验证证书 部分——中间人可伪造证书
verify-ca SSL + 验证证书链 好,但不检查主机名
verify-full SSL + 验证证书 + 验证主机名 生产环境应使用

prefer 是最危险的默认值。Django、SQLAlchemy、ActiveRecord 几乎都不显式覆盖它。攻击者截断 SSL 协商,应用静默回退明文,日志里没有任何异常。

require 加密了流量但不验证证书——中间人可以提交自己的证书,你的应用照收不误。

生产连接字符串示例:

# psql 命令行
psql "host=db.example.com dbname=mydb user=appuser \
  sslmode=verify-full sslrootcert=/path/to/root.crt"

# URL 格式(应用配置和 ORM 常用)
postgresql://appuser@db.example.com/mydb?sslmode=verify-full&sslrootcert=/path/to/root.crt

服务端 hostssl + 客户端 verify-full,这才是"SSL 已配置"应有的含义。

验证:别信配置,查运行状态

每次 SSL 相关变更后,直接验证。PostgreSQL 提供 pg_stat_ssl 视图,展示每条连接的加密状态:

-- 当前会话是否加密?
SELECT ssl, version, cipher, bits
FROM pg_stat_ssl
WHERE pid = pg_backend_pid();

-- 是否有远程连接正在明文传输?
SELECT pid, usename, application_name, client_addr, ssl
FROM pg_stat_ssl
JOIN pg_stat_activity USING (pid)
WHERE ssl = false
  AND client_addr IS NOT NULL;

第二条查询返回任何行,就意味着那些连接此刻在裸奔。把它放进监控栈,定时执行,对非本地地址的结果告警。几分钟设置,换来持续证据而非一次性配置审查。

证书过期检查——别等它炸了才发现:

openssl x509 -in server.crt -noout -dates

常见失误清单

  • pg_hba.confhost 而非 hostssl:证书正确、配置正确,仍然接受明文。最常见缺口,值得反复强调。
  • 应用留 sslmode=prefer:默认值,几乎没人显式覆盖。攻击者剥离 SSL 协商,应用静默回退明文,零日志痕迹。
  • sslmode=require 就收工:加密了但不验证证书,中间人伪造证书照样通过。比 prefer 好,但不是 verify-full
  • 忘记证书过期:证书到期后 PostgreSQL 拒绝所有 SSL 连接,在最不巧的日子炸掉。要么自动化轮换,要么提前 30 天设日历提醒——"到时候再说"一定会忘。
  • 只配主库不配副本:主库收紧了 SSL,几个月后加 standby,pg_hba.conf 没同步。读流量走副本,副本是敞开的。副本安静、不出事,配置漂移就这么积累。

SSL 不覆盖的范围

SSL 保护应用与数据库之间的传输通道。它对静态数据没有立场——加密存储、加密表空间、操作系统级加密是独立问题。它也不替代网络控制:私有子网、5432 端口防火墙规则、最小暴露面仍是基础。SSL 在这个结构里是一层,不是替代品。

配置顺序与自检

正确的完整顺序:

  1. 生成证书文件,放入数据目录,锁定权限。
  2. postgresql.conf 设置 ssl = on 及相关路径。
  3. pg_hba.conf 远程连接改 hostssl
  4. 重启 PostgreSQL。
  5. 客户端连接字符串设 sslmode=verify-full 并指定 sslrootcert
  6. 执行 pg_stat_ssl 查询确认。

按这个顺序做完,再跑验证查询。那才是"SSL 已配置"的实际含义。


相关推荐